changeset 5594:87aef0cb5a6a

Merge stable
author Mads Kiilerich <madski@unity3d.com>
date Fri, 27 Nov 2015 01:39:21 +0100
parents 007d1a34a35a (diff) 1666c8c7d925 (current diff)
children 6c7efed20abc
files docs/setup.rst kallithea/controllers/admin/gists.py kallithea/templates/admin/gists/edit.html
diffstat 82 files changed, 1150 insertions(+), 677 deletions(-) [+]
line wrap: on
line diff
--- a/CONTRIBUTORS	Fri Nov 27 01:34:47 2015 +0100
+++ b/CONTRIBUTORS	Fri Nov 27 01:39:21 2015 +0100
@@ -22,6 +22,7 @@
     Étienne Gilli <etienne.gilli@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
--- a/README.rst	Fri Nov 27 01:34:47 2015 +0100
+++ b/README.rst	Fri Nov 27 01:39:21 2015 +0100
@@ -172,7 +172,7 @@
 - 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
@@ -191,7 +191,7 @@
 ``$VIRTUAL_ENV/lib/python2.7/site-packages/Kallithea-0.1-py2.7.egg/kallithea``.
 
 One-time conversion
-~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^
 
 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
@@ -209,7 +209,7 @@
    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,
--- a/development.ini	Fri Nov 27 01:34:47 2015 +0100
+++ b/development.ini	Fri Nov 27 01:39:21 2015 +0100
@@ -163,6 +163,7 @@
 #cheaper-step = 1
 
 ## COMMON ##
+#host = 127.0.0.1
 host = 0.0.0.0
 port = 5000
 
@@ -280,12 +281,6 @@
 #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
@@ -301,6 +296,18 @@
 ## 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        ####
 ####################################
@@ -560,16 +567,16 @@
 class = StreamHandler
 args = (sys.stderr,)
 #level = INFO
+level = DEBUG
 #formatter = generic
-level = DEBUG
 formatter = color_formatter
 
 [handler_console_sql]
 class = StreamHandler
 args = (sys.stderr,)
 #level = WARN
+level = DEBUG
 #formatter = generic
-level = DEBUG
 formatter = color_formatter_sql
 
 ################
--- a/docs/api/api.rst	Fri Nov 27 01:34:47 2015 +0100
+++ b/docs/api/api.rst	Fri Nov 27 01:39:21 2015 +0100
@@ -10,7 +10,7 @@
 
 
 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
@@ -36,7 +36,7 @@
 
 
 API access
-++++++++++
+----------
 
 Clients must send JSON encoded JSON-RPC requests::
 
@@ -76,7 +76,7 @@
 
 
 API client
-++++++++++
+----------
 
 Kallithea comes with a ``kallithea-api`` command line tool, providing a convenient
 way to call the JSON-RPC API.
@@ -110,11 +110,11 @@
 
 
 API methods
-+++++++++++
+-----------
 
 
 pull
-----
+^^^^
 
 Pull the given repo from remote location. Can be used to automatically keep
 remote repos up to date.
@@ -136,7 +136,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 +159,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 +181,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 +212,7 @@
     error :  null
 
 get_ip
-------
+^^^^^^
 
 Return IP address as seen from Kallithea server, together with all
 defined IP addresses for given user.
@@ -244,7 +244,7 @@
     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.
@@ -288,7 +288,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 +325,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 +371,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 +415,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 +439,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 +481,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 +507,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 +537,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 +564,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 +591,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.
@@ -680,7 +680,7 @@
     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 +717,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 +748,7 @@
     error:  null
 
 create_repo
------------
+^^^^^^^^^^^
 
 Create a repository. If the repository name contains "/", all needed repository
 groups will be created. For example "foo/bar/baz" will create repository groups
@@ -800,7 +800,7 @@
     error:  null
 
 update_repo
------------
+^^^^^^^^^^^
 
 Update a repository.
 This command can only be executed using the api_key of a user with admin rights,
@@ -860,7 +860,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
@@ -896,7 +896,7 @@
     error:  null
 
 delete_repo
------------
+^^^^^^^^^^^
 
 Delete a repository.
 This command can only be executed using the api_key of a user with admin rights,
@@ -923,7 +923,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.
@@ -949,7 +949,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.
@@ -974,7 +974,7 @@
     error:  null
 
 grant_user_group_permission
----------------------------
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 Grant permission for a user group on the given repository, or update the
 existing one if found.
@@ -1001,7 +1001,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.
--- a/docs/index.rst	Fri Nov 27 01:34:47 2015 +0100
+++ b/docs/index.rst	Fri Nov 27 01:39:21 2015 +0100
@@ -63,7 +63,7 @@
 
 
 Other topics
-------------
+************
 
 * :ref:`genindex`
 * :ref:`search`
--- a/docs/installation_iis.rst	Fri Nov 27 01:34:47 2015 +0100
+++ b/docs/installation_iis.rst	Fri Nov 27 01:39:21 2015 +0100
@@ -28,7 +28,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.
@@ -44,7 +44,7 @@
     as long as the Kallithea requirements are met by the existing pool.
 
 ISAPI handler
-.............
+^^^^^^^^^^^^^
 
 The ISAPI handler can be generated using::
 
@@ -68,7 +68,7 @@
 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.
--- a/docs/installation_win.rst	Fri Nov 27 01:34:47 2015 +0100
+++ b/docs/installation_win.rst	Fri Nov 27 01:39:21 2015 +0100
@@ -6,7 +6,7 @@
 
 
 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.
@@ -146,7 +146,7 @@
           normal.
 
 Step 8 -- Install git (optional)
---------------------------------
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 Mercurial being a python package, it was installed automatically when doing "pip install kallithea".
 
@@ -155,7 +155,7 @@
 See http://git-scm.com/book/en/v2/Getting-Started-Installing-Git#Installing-on-Windows for instructions.
 
 Step 9 -- Configuring Kallithea
--------------------------------
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 Steps taken from `<setup.html>`_
 
@@ -194,7 +194,7 @@
 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::
 
@@ -222,7 +222,7 @@
 
 
 Upgrading
-:::::::::
+---------
 
 Stop running Kallithea
 Open a CommandPrompt like in Step 7 (cd to C:\Kallithea\Env\Scripts and activate) and type::
--- a/docs/installation_win_old.rst	Fri Nov 27 01:34:47 2015 +0100
+++ b/docs/installation_win_old.rst	Fri Nov 27 01:39:21 2015 +0100
@@ -6,7 +6,7 @@
 
 
 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
 
@@ -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
 
@@ -233,7 +233,7 @@
 it again.
 
 Step 9 -- Running Kallithea
----------------------------
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 In the previous command prompt, being in the C:\\Kallithea\\Bin folder,
 just type::
@@ -263,7 +263,7 @@
 
 
 Upgrading
-:::::::::
+---------
 
 Stop running Kallithea
 Open a CommandPrompt like in Step7 (VS2008 path + activate) and type::
--- a/docs/setup.rst	Fri Nov 27 01:34:47 2015 +0100
+++ b/docs/setup.rst	Fri Nov 27 01:39:21 2015 +0100
@@ -346,7 +346,7 @@
 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
@@ -383,7 +383,7 @@
 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.
@@ -393,7 +393,7 @@
 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
@@ -746,11 +746,6 @@
    When running apache as root, please make sure it doesn't run Kallithea as
    root, for examply by adding: ``user=www-data group=www-data`` to the configuration.
 
-.. 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
--- a/docs/usage/general.rst	Fri Nov 27 01:34:47 2015 +0100
+++ b/docs/usage/general.rst	Fri Nov 27 01:39:21 2015 +0100
@@ -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	Fri Nov 27 01:34:47 2015 +0100
+++ b/docs/usage/performance.rst	Fri Nov 27 01:39:21 2015 +0100
@@ -38,7 +38,6 @@
     scaled horizontally on one (recommended) or multiple machines. In order
     to scale horizontally you need to do the following:
 
-    - Each instance needs its own .ini file and unique ``instance_id`` set.
     - 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
--- a/docs/usage/vcs_support.rst	Fri Nov 27 01:34:47 2015 +0100
+++ b/docs/usage/vcs_support.rst	Fri Nov 27 01:39:21 2015 +0100
@@ -23,7 +23,7 @@
 
 
 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
@@ -51,7 +51,7 @@
 
 
 Working with Mercurial subrepositories
-``````````````````````````````````````
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 This section explains how to use Mercurial subrepositories_ in Kallithea.
 
--- a/kallithea/bin/template.ini.mako	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/bin/template.ini.mako	Fri Nov 27 01:39:21 2015 +0100
@@ -278,12 +278,6 @@
 #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>
@@ -299,6 +293,18 @@
 <%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>
--- a/kallithea/config/conf.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/config/conf.py	Fri Nov 27 01:39:21 2015 +0100
@@ -25,19 +25,21 @@
 :license: GPLv3, see LICENSE.md for more details.
 """
 
-from kallithea.lib.utils2 import __get_lem
+from kallithea.lib.utils2 import __get_lem, __get_index_filenames
 
 
 # language map is also used by whoosh indexer, which for those specified
 # extensions will index it's content
 LANGUAGES_EXTENSIONS_MAP = __get_lem()
 
-#==============================================================================
-# WHOOSH INDEX EXTENSIONS
-#==============================================================================
-# EXTENSIONS WE WANT TO INDEX CONTENT OFF USING WHOOSH
+# Whoosh index targets
+
+# 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 = __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 = [
--- a/kallithea/config/deployment.ini_tmpl	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/config/deployment.ini_tmpl	Fri Nov 27 01:39:21 2015 +0100
@@ -274,12 +274,6 @@
 #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
@@ -295,6 +289,18 @@
 ## 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        ####
 ####################################
--- a/kallithea/config/environment.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/config/environment.py	Fri Nov 27 01:39:21 2015 +0100
@@ -34,7 +34,7 @@
 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
+    load_rcextensions, check_git_version, set_vcs_config, set_indexer_config
 from kallithea.lib.utils2 import engine_from_config, str2bool
 from kallithea.lib.db_manage import DbManage
 from kallithea.model import init_model
@@ -118,7 +118,7 @@
     config['base_path'] = repos_path
     set_app_settings(config)
 
-    instance_id = kallithea.CONFIG.get('instance_id')
+    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
@@ -130,6 +130,7 @@
     # pylons
     kallithea.CONFIG.update(config)
     set_vcs_config(kallithea.CONFIG)
+    set_indexer_config(kallithea.CONFIG)
 
     #check git version
     check_git_version()
--- a/kallithea/controllers/admin/auth_settings.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/controllers/admin/auth_settings.py	Fri Nov 27 01:39:21 2015 +0100
@@ -28,8 +28,8 @@
 import traceback
 
 from pylons import request, tmpl_context as c, url
-from pylons.controllers.util import redirect
 from pylons.i18n.translation import _
+from webob.exc import HTTPFound
 
 from kallithea.lib import helpers as h
 from kallithea.lib.compat import formatted_json
@@ -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	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/controllers/admin/defaults.py	Fri Nov 27 01:39:21 2015 +0100
@@ -31,8 +31,8 @@
 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 webob.exc import HTTPFound
 
 from kallithea.lib import helpers as h
 from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator
@@ -112,7 +112,7 @@
             h.flash(_('Error occurred during update of defaults'),
                     category='error')
 
-        return redirect(url('defaults'))
+        raise HTTPFound(location=url('defaults'))
 
     def delete(self, id):
         """DELETE /defaults/id: Delete an existing item"""
--- a/kallithea/controllers/admin/gists.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/controllers/admin/gists.py	Fri Nov 27 01:39:21 2015 +0100
@@ -31,8 +31,8 @@
 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 webob.exc import HTTPFound, HTTPNotFound, HTTPForbidden
 
 from kallithea.model.forms import GistForm
 from kallithea.model.gist import GistModel
@@ -44,7 +44,6 @@
 from kallithea.lib.utils import jsonify
 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 sqlalchemy.sql.expression import or_
 from kallithea.lib.vcs.exceptions import VCSError, NodeNotChangedError
 
@@ -70,7 +69,7 @@
     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 c.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
 
@@ -144,8 +143,8 @@
         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()
@@ -185,7 +184,7 @@
         else:
             raise HTTPForbidden()
 
-        return redirect(url('gists'))
+        raise HTTPFound(location=url('gists'))
 
     @LoginRequired()
     def show(self, gist_id, revision='tip', format='html', f_path=None):
@@ -270,7 +269,7 @@
                 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
 
--- a/kallithea/controllers/admin/my_account.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/controllers/admin/my_account.py	Fri Nov 27 01:39:21 2015 +0100
@@ -32,8 +32,8 @@
 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 webob.exc import HTTPFound
 
 from kallithea import EXTERN_TYPE_INTERNAL
 from kallithea.lib import helpers as h
@@ -69,7 +69,7 @@
         if c.user.username == User.DEFAULT_USER:
             h.flash(_("You can't edit this user since it's"
                       " crucial for entire application"), category='warning')
-            return redirect(url('users'))
+            raise HTTPFound(location=url('users'))
         c.EXTERN_TYPE_INTERNAL = EXTERN_TYPE_INTERNAL
 
     def _load_my_repos_data(self, watched=False):
@@ -144,7 +144,7 @@
                 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,
@@ -225,7 +225,7 @@
             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')
@@ -233,7 +233,7 @@
         user_model.delete_extra_email(self.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'
@@ -257,7 +257,7 @@
         ApiKeyModel().create(self.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')
@@ -274,4 +274,4 @@
             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	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/controllers/admin/notifications.py	Fri Nov 27 01:39:21 2015 +0100
@@ -30,8 +30,7 @@
 
 from pylons import request
 from pylons import tmpl_context as c
-from pylons.controllers.util import abort
-from webob.exc import HTTPBadRequest
+from webob.exc import HTTPBadRequest, HTTPForbidden
 
 from kallithea.model.db import Notification
 from kallithea.model.notification import NotificationModel
@@ -168,7 +167,7 @@
 
                 return render('admin/notifications/show_notification.html')
 
-        return abort(403)
+        raise HTTPForbidden()
 
     def edit(self, notification_id, format='html'):
         """GET /_admin/notifications/id/edit: Form to edit an existing item"""
--- a/kallithea/controllers/admin/permissions.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/controllers/admin/permissions.py	Fri Nov 27 01:39:21 2015 +0100
@@ -32,8 +32,8 @@
 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 webob.exc import HTTPFound
 
 from kallithea.lib import helpers as h
 from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator
@@ -139,7 +139,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}
--- a/kallithea/controllers/admin/repo_groups.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/controllers/admin/repo_groups.py	Fri Nov 27 01:39:21 2015 +0100
@@ -33,8 +33,8 @@
 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 webob.exc import HTTPFound, HTTPForbidden, HTTPNotFound, HTTPInternalServerError
 
 import kallithea
 from kallithea.lib import helpers as h
@@ -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
 
@@ -189,10 +188,10 @@
                     % 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))
+            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"""
@@ -209,7 +208,7 @@
             if HasRepoGroupPermissionAll('group.admin')(group_name, 'group create'):
                 pass
             else:
-                return abort(403)
+                raise HTTPForbidden()
 
         self.__load_defaults()
         return render('admin/repo_groups/repo_group_add.html')
@@ -266,7 +265,7 @@
             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')
     def delete(self, group_name):
@@ -283,13 +282,13 @@
         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)
@@ -303,8 +302,8 @@
                     % 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):
         """
@@ -404,7 +403,7 @@
             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 !
@@ -418,7 +417,7 @@
         #              repo_name, self.ip_addr, self.sa)
         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')
     def delete_perms(self, group_name):
--- a/kallithea/controllers/admin/repos.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/controllers/admin/repos.py	Fri Nov 27 01:39:21 2015 +0100
@@ -29,11 +29,10 @@
 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 sqlalchemy.sql.expression import func
+from webob.exc import HTTPFound, HTTPInternalServerError, HTTPForbidden, HTTPNotFound
 
 from kallithea.lib import helpers as h
 from kallithea.lib.auth import LoginRequired, \
@@ -71,7 +70,7 @@
 
         if repo_obj is None:
             h.not_mapped_error(repo_name)
-            return redirect(url('repos'))
+            raise HTTPFound(location=url('repos'))
 
         return repo_obj
 
@@ -152,9 +151,9 @@
             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))
 
@@ -282,7 +281,7 @@
             log.error(traceback.format_exc())
             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')
     def delete(self, repo_name):
@@ -299,7 +298,7 @@
         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
@@ -327,8 +326,8 @@
                     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')
     def edit(self, repo_name):
@@ -372,7 +371,7 @@
         #              repo_name, self.ip_addr, self.sa)
         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))
 
     def edit_permissions_revoke(self, repo_name):
         try:
@@ -409,7 +408,7 @@
         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')
@@ -431,7 +430,7 @@
             if isinstance(e, formencode.Invalid):
                 msg += ". " + e.msg
             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')
     def delete_repo_field(self, repo_name, field_id):
@@ -443,7 +442,7 @@
             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')
     def edit_advanced(self, repo_name):
@@ -468,7 +467,7 @@
 
         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,
@@ -495,7 +494,7 @@
             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')
@@ -521,7 +520,7 @@
             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')
     def edit_advanced_locking(self, repo_name):
@@ -542,7 +541,7 @@
             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')
     def toggle_locking(self, repo_name):
@@ -567,7 +566,7 @@
             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')
     def edit_caches(self, repo_name):
@@ -577,7 +576,7 @@
         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')
@@ -586,7 +585,7 @@
                 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')
@@ -603,7 +602,7 @@
                 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')
@@ -636,6 +635,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	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/controllers/admin/settings.py	Fri Nov 27 01:39:21 2015 +0100
@@ -31,8 +31,8 @@
 
 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 webob.exc import HTTPFound
 
 from kallithea.lib import helpers as h
 from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator
@@ -206,7 +206,7 @@
             if invalidate_cache:
                 log.debug('invalidating all repositories cache')
                 for repo in Repository.get_all():
-                    ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
+                    ScmModel().mark_for_invalidation(repo.repo_name)
 
             filesystem_repos = ScmModel().repo_scan()
             added, removed = repo2db_mapper(filesystem_repos, rm_obsolete,
@@ -218,7 +218,7 @@
                  for repo_name in added) or '-',
                  ', '.join(h.escape(safe_unicode(repo_name)) for repo_name in removed) or '-')),
                 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())
@@ -278,7 +278,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())
@@ -336,7 +336,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())
@@ -359,7 +359,7 @@
                                '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()\
                 .get_email_tmpl(EmailNotificationModel.TYPE_DEFAULT,
@@ -374,7 +374,7 @@
                      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())
@@ -425,7 +425,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())
@@ -449,7 +449,7 @@
             full_index = request.POST.get('full_index', False)
             run_task(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())
--- a/kallithea/controllers/admin/user_groups.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/controllers/admin/user_groups.py	Fri Nov 27 01:39:21 2015 +0100
@@ -31,8 +31,8 @@
 
 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 webob.exc import HTTPFound
 
 from sqlalchemy.orm import joinedload
 from sqlalchemy.sql.expression import func
@@ -163,7 +163,7 @@
             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'):
@@ -224,7 +224,7 @@
             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')
     def delete(self, id):
@@ -246,7 +246,7 @@
             log.error(traceback.format_exc())
             h.flash(_('An error occurred during deletion of user group'),
                     category='error')
-        return redirect(url('users_groups'))
+        raise HTTPFound(location=url('users_groups'))
 
     def show(self, id, format='html'):
         """GET /user_groups/id: Show a specific item"""
@@ -312,13 +312,13 @@
                                                  form['perms_updates'])
         except RepoGroupAssignmentError:
             h.flash(_('Target group cannot be the same'), category='error')
-            return redirect(url('edit_user_group_perms', id=id))
+            raise HTTPFound(location=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)
         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')
     def delete_perms(self, id):
@@ -444,7 +444,7 @@
             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')
     def edit_advanced(self, id):
--- a/kallithea/controllers/admin/users.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/controllers/admin/users.py	Fri Nov 27 01:39:21 2015 +0100
@@ -31,10 +31,9 @@
 
 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 sqlalchemy.sql.expression import func
-from webob.exc import HTTPNotFound
+from webob.exc import HTTPFound, HTTPNotFound
 
 import kallithea
 from kallithea.lib.exceptions import DefaultUserException, \
@@ -148,7 +147,7 @@
             log.error(traceback.format_exc())
             h.flash(_('Error occurred during creation of user %s') \
                     % request.POST.get('username'), category='error')
-        return redirect(url('users'))
+        raise HTTPFound(location=url('users'))
 
     def new(self, format='html'):
         """GET /users/new: Form to create a new item"""
@@ -201,7 +200,7 @@
             log.error(traceback.format_exc())
             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"""
@@ -222,7 +221,7 @@
             log.error(traceback.format_exc())
             h.flash(_('An error occurred during deletion of user'),
                     category='error')
-        return redirect(url('users'))
+        raise HTTPFound(location=url('users'))
 
     def show(self, id, format='html'):
         """GET /users/id: Show a specific item"""
@@ -306,7 +305,7 @@
         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)
@@ -324,7 +323,7 @@
             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
@@ -387,7 +386,7 @@
             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)
@@ -420,7 +419,7 @@
             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"""
@@ -431,7 +430,7 @@
         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)
@@ -470,8 +469,8 @@
                     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"""
@@ -483,5 +482,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/api.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/controllers/api/api.py	Fri Nov 27 01:39:21 2015 +0100
@@ -25,7 +25,6 @@
 :license: GPLv3, see LICENSE.md for more details.
 """
 
-
 import time
 import traceback
 import logging
@@ -705,10 +704,10 @@
 
     @HasPermissionAllDecorator('hg.admin')
     def update_user(self, apiuser, userid, username=Optional(None),
-                    email=Optional(None),password=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.
@@ -1074,7 +1073,7 @@
             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):
@@ -1455,7 +1454,7 @@
         """
         if not HasPermissionAnyApi('hg.admin')(user=apiuser):
             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'
                 )
@@ -1562,7 +1561,7 @@
                 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'
                 )
@@ -1660,7 +1659,7 @@
                                      'repository.read')(user=apiuser,
                                                         repo_name=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'
                 )
@@ -1741,7 +1740,7 @@
         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):
+                                                               repo_name=repo.repo_name):
                 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
 
         try:
@@ -2175,7 +2174,7 @@
             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,
@@ -2315,7 +2314,7 @@
     # permission check inside
     def grant_user_group_permission_to_repo_group(
             self, apiuser, repogroupid, usergroupid, perm,
-            apply_to_children=Optional('none'),):
+            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
@@ -2381,9 +2380,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:
--- a/kallithea/controllers/changelog.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/controllers/changelog.py	Fri Nov 27 01:39:21 2015 +0100
@@ -29,9 +29,8 @@
 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 webob.exc import HTTPFound, HTTPNotFound, HTTPBadRequest
 
 import kallithea.lib.helpers as h
 from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
@@ -99,8 +98,8 @@
         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))
+                raise HTTPFound(location=url('changelog_home', repo_name=repo_name, **request.GET))
+            raise HTTPFound(location=url('changelog_file_home', repo_name=repo_name, revision=revision, f_path=f_path, **request.GET))
 
         limit = 2000
         default = 100
@@ -118,7 +117,7 @@
             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':
@@ -140,7 +139,7 @@
                         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,
@@ -155,11 +154,11 @@
             c.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'))] + \
--- a/kallithea/controllers/changeset.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/controllers/changeset.py	Fri Nov 27 01:39:21 2015 +0100
@@ -29,13 +29,12 @@
 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 webob.exc import HTTPFound, HTTPForbidden, HTTPBadRequest, HTTPNotFound
+
 from kallithea.lib.utils import jsonify
-
 from kallithea.lib.vcs.exceptions import RepositoryError, \
     ChangesetDoesNotExistError
 
@@ -207,7 +206,7 @@
             if not c.cs_ranges:
                 raise RepositoryError('Changeset range returned empty result')
 
-        except(ChangesetDoesNotExistError,), e:
+        except ChangesetDoesNotExistError:
             log.debug(traceback.format_exc())
             msg = _('Such revision does not exist for this repository')
             h.flash(msg, category='error')
@@ -226,7 +225,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)])
@@ -254,7 +252,7 @@
             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,
                 ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
@@ -383,7 +381,7 @@
                 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,
+                raise HTTPFound(location=h.url('changeset_home', repo_name=repo_name,
                                       revision=revision))
         action_logger(self.authuser,
                       'user_commented_revision:%s' % revision,
@@ -392,7 +390,7 @@
         Session().commit()
 
         if not request.environ.get('HTTP_X_PARTIAL_XHR'):
-            return redirect(h.url('changeset_home', repo_name=repo_name,
+            raise HTTPFound(location=h.url('changeset_home', repo_name=repo_name,
                                   revision=revision))
         #only ajax below
         data = {
--- a/kallithea/controllers/compare.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/controllers/compare.py	Fri Nov 27 01:39:21 2015 +0100
@@ -30,11 +30,11 @@
 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 webob.exc import HTTPFound, HTTPBadRequest
 
+from kallithea.lib.utils2 import safe_str
 from kallithea.lib.vcs.utils.hgcompat import unionrepo
 from kallithea.lib import helpers as h
 from kallithea.lib.base import BaseRepoController, render
@@ -114,10 +114,10 @@
                 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],
@@ -206,19 +206,19 @@
             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))
+            raise HTTPFound(location=url('compare_home', repo_name=c.repo_name))
 
         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))
+            raise HTTPFound(location=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))
+            raise HTTPFound(location=url('compare_home', repo_name=c.repo_name))
 
         c.a_rev = self._get_ref_rev(org_repo, org_ref_type, org_ref_name,
             returnempty=True)
--- a/kallithea/controllers/files.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/controllers/files.py	Fri Nov 27 01:39:21 2015 +0100
@@ -33,9 +33,9 @@
 
 from pylons import request, response, tmpl_context as c, url
 from pylons.i18n.translation import _
-from pylons.controllers.util import redirect
+from webob.exc import HTTPFound
+
 from kallithea.lib.utils import jsonify, action_logger
-
 from kallithea.lib import diffs
 from kallithea.lib import helpers as h
 
@@ -92,7 +92,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 +112,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()
@@ -137,6 +137,7 @@
         c.f_path = f_path
         c.annotate = annotate
         cur_rev = c.changeset.revision
+        c.fulldiff = request.GET.get('fulldiff')
 
         # prev link
         try:
@@ -306,7 +307,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 +317,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))
 
@@ -352,7 +353,7 @@
             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')
@@ -366,7 +367,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 +377,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 +387,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
@@ -405,7 +406,7 @@
 
             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,
@@ -418,7 +419,7 @@
             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')
@@ -433,7 +434,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'))
 
         r_post = request.POST
@@ -462,11 +463,11 @@
 
             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
             filename = os.path.basename(filename)
@@ -492,14 +493,14 @@
             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')
@@ -620,7 +621,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,7 +656,7 @@
                 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':
--- a/kallithea/controllers/forks.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/controllers/forks.py	Fri Nov 27 01:39:21 2015 +0100
@@ -31,8 +31,8 @@
 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 webob.exc import HTTPFound
 
 import kallithea.lib.helpers as h
 
@@ -77,7 +77,7 @@
 
         if c.repo_info is None:
             h.not_mapped_error(repo_name)
-            return redirect(url('repos'))
+            raise HTTPFound(location=url('repos'))
 
         c.default_user_id = User.get_default_user().user_id
         c.in_public_journal = UserFollowing.query()\
@@ -137,7 +137,7 @@
         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)
 
@@ -186,6 +186,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	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/controllers/home.py	Fri Nov 27 01:39:21 2015 +0100
@@ -130,6 +130,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({
--- a/kallithea/controllers/login.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/controllers/login.py	Fri Nov 27 01:39:21 2015 +0100
@@ -31,10 +31,9 @@
 import formencode
 
 from formencode import htmlfill
+from pylons.i18n.translation import _
+from pylons import request, session, tmpl_context as c, url
 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.lib.auth import AuthUser, HasPermissionAnyDecorator
@@ -79,11 +78,10 @@
         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:
+        if self.authuser.is_authenticated and ip_allowed:
             raise HTTPFound(location=c.came_from)
 
         if request.POST:
@@ -152,7 +150,7 @@
                 h.flash(_('You have successfully registered into Kallithea'),
                         category='success')
                 Session().commit()
-                return redirect(url('login_home'))
+                raise HTTPFound(location=url('login_home'))
 
             except formencode.Invalid as errors:
                 return htmlfill.render(
@@ -196,7 +194,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 +247,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	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/controllers/pullrequests.py	Fri Nov 27 01:39:21 2015 +0100
@@ -30,11 +30,9 @@
 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 webob.exc import HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest
 
 from kallithea.lib.vcs.utils.hgcompat import unionrepo
 from kallithea.lib.compat import json
@@ -237,7 +235,7 @@
         except EmptyRepositoryError as e:
             h.flash(h.literal(_('There are no changesets yet')),
                     category='warning')
-            redirect(url('summary_home', repo_name=org_repo.repo_name))
+            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
@@ -369,9 +367,9 @@
             h.flash(_('Error occurred while creating pull request'),
                     category='error')
             log.error(traceback.format_exc())
-            return redirect(url('pullrequest_home', repo_name=repo_name))
+            raise HTTPFound(location=url('pullrequest_home', repo_name=repo_name))
 
-        return redirect(pull_request.url())
+        raise HTTPFound(location=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)
@@ -456,7 +454,7 @@
             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),
@@ -470,7 +468,7 @@
         h.flash(_('Pull request update created'),
                 category='success')
 
-        return redirect(pull_request.url())
+        raise HTTPFound(location=pull_request.url())
 
     # pullrequest_post for PR editing
     @LoginRequired()
@@ -513,7 +511,7 @@
         Session().commit()
         h.flash(_('Pull request updated'), category='success')
 
-        return redirect(pull_request.url())
+        raise HTTPFound(location=pull_request.url())
 
     @LoginRequired()
     @NotAnonymous()
@@ -528,7 +526,7 @@
             Session().commit()
             h.flash(_('Successfully deleted pull request'),
                     category='success')
-            return redirect(url('my_pullrequests'))
+            raise HTTPFound(location=url('my_pullrequests'))
         raise HTTPForbidden()
 
     @LoginRequired()
@@ -762,7 +760,7 @@
         Session().commit()
 
         if not request.environ.get('HTTP_X_PARTIAL_XHR'):
-            return redirect(pull_request.url())
+            raise HTTPFound(location=pull_request.url())
 
         data = {
            'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
--- a/kallithea/controllers/search.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/controllers/search.py	Fri Nov 27 01:39:21 2015 +0100
@@ -139,7 +139,7 @@
                 log.error('Empty Index data')
                 c.runtime = _('There is no index to search in. '
                               'Please run whoosh indexer')
-            except (Exception):
+            except Exception:
                 log.error(traceback.format_exc())
                 c.runtime = _('An error occurred during search operation.')
 
--- a/kallithea/controllers/summary.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/controllers/summary.py	Fri Nov 27 01:39:21 2015 +0100
@@ -114,8 +114,9 @@
     def index(self, repo_name):
         _load_changelog_summary()
 
-        username = ''
-        if self.authuser.username != User.DEFAULT_USER:
+        if self.authuser.is_default_user:
+            username = ''
+        else:
             username = safe_str(self.authuser.username)
 
         _def_clone_uri = _def_clone_uri_by_id = c.clone_uri_tmpl
--- a/kallithea/i18n/de/LC_MESSAGES/kallithea.po	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/i18n/de/LC_MESSAGES/kallithea.po	Fri Nov 27 01:39:21 2015 +0100
@@ -11,7 +11,7 @@
 "PO-Revision-Date: 2015-09-08 10:56+0200\n"
 "Last-Translator: Robert Rauch <mail@robertrauch.de>\n"
 "Language-Team: German "
-"<https://hosted.weblate.org/projects/kallithea/stable/de/>\n"
+"<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"
--- a/kallithea/lib/auth.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/lib/auth.py	Fri Nov 27 01:39:21 2015 +0100
@@ -35,12 +35,12 @@
 from decorator import decorator
 
 from pylons import url, request, session
-from pylons.controllers.util import abort, redirect
 from pylons.i18n.translation import _
 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.lib.vcs.utils.lazy import LazyProperty
@@ -465,8 +465,7 @@
     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.
 
@@ -508,9 +507,7 @@
         if not is_user_loaded:
             is_user_loaded =  self._fill_data(self.anonymous_user)
 
-        # The anonymous user is always "logged in".
-        if self.user_id == self.anonymous_user.user_id:
-            self.is_authenticated = True
+        self.is_default_user = (self.user_id == self.anonymous_user.user_id)
 
         if not self.username:
             self.username = 'None'
@@ -623,17 +620,12 @@
 
     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
+            % (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,
         }
 
@@ -647,9 +639,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
@@ -716,7 +706,7 @@
     if message:
         h.flash(h.literal(message), category='warning')
     log.debug('Redirecting to login page, origin: %s', p)
-    return redirect(url('login_home', came_from=p))
+    raise HTTPFound(location=url('login_home', came_from=p))
 
 
 class LoginRequired(object):
@@ -758,13 +748,13 @@
             else:
                 # controller does not allow API access
                 log.warning('API access to %s is not allowed', loc)
-                return abort(403)
+                raise HTTPForbidden()
 
         # 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 HTTPMethodNotAllowed()
 
         # Make sure CSRF token never appears in the URL. If so, invalidate it.
         if secure_form.token_key in request.GET:
@@ -785,17 +775,17 @@
             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)
+                raise HTTPForbidden()
 
         # 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 HTTPBadRequest()
 
         # regular user authentication
-        if user.is_authenticated:
+        if user.is_authenticated or user.is_default_user:
             log.info('user %s authenticated with regular auth @ %s', user, loc)
             return func(*fargs, **fkwargs)
         else:
@@ -816,9 +806,7 @@
 
         log.debug('Checking if user is not anonymous @%s', cls)
 
-        anonymous = self.user.username == User.DEFAULT_USER
-
-        if anonymous:
+        if self.user.is_default_user:
             return redirect_to_login(_('You need to be a registered user to '
                     'perform this action'))
         else:
@@ -848,13 +836,10 @@
 
         else:
             log.debug('Permission denied for %s %s', cls, self.user)
-            anonymous = self.user.username == User.DEFAULT_USER
-
-            if anonymous:
+            if self.user.is_default_user:
                 return 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"""
--- a/kallithea/lib/auth_modules/auth_ldap.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/lib/auth_modules/auth_ldap.py	Fri Nov 27 01:39:21 2015 +0100
@@ -356,7 +356,7 @@
         except (LdapUsernameError, LdapPasswordError, LdapImportError):
             log.error(traceback.format_exc())
             return None
-        except (Exception,):
+        except Exception:
             log.error(traceback.format_exc())
             return None
 
--- a/kallithea/lib/base.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/lib/base.py	Fri Nov 27 01:39:21 2015 +0100
@@ -40,7 +40,6 @@
 
 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 _
 
@@ -117,7 +116,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()
@@ -392,7 +393,9 @@
         # 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):
+        # 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):
             try:
                 return AuthUser.from_cookie(session_authuser)
             except UserCreationError as e:
@@ -479,7 +482,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
--- a/kallithea/lib/compat.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/lib/compat.py	Fri Nov 27 01:39:21 2015 +0100
@@ -540,7 +540,8 @@
                 return cmp(type(self), type(other))
             return cmp(list(self), list(other))
 
-        def __repr__(self, _track=[]):
+        def __repr__(self, _track=None):
+            _track = _track or []
             if id(self) in _track:
                 return '...'
             _track.append(id(self))
@@ -560,8 +561,9 @@
         def __copy__(self):
             return self.__class__(self)
 
-        def __deepcopy__(self, memo={}):
+        def __deepcopy__(self, memo=None):
             from copy import deepcopy
+            memo = memo or {}
             result = self.__class__()
             memo[id(self)] = result
             result.__init__(deepcopy(tuple(self), memo))
--- a/kallithea/lib/db_manage.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/lib/db_manage.py	Fri Nov 27 01:39:21 2015 +0100
@@ -61,14 +61,14 @@
 
 
 class DbManage(object):
-    def __init__(self, log_sql, dbconf, root, tests=False, SESSION=None, cli_args={}):
+    def __init__(self, log_sql, 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)
 
         force_ask = self.cli_args.get('force_ask')
--- a/kallithea/lib/dbmigrate/migrate/versioning/schema.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/lib/dbmigrate/migrate/versioning/schema.py	Fri Nov 27 01:39:21 2015 +0100
@@ -67,7 +67,7 @@
         else:
             try:
                 self.table.drop()
-            except (sa_exceptions.SQLError):
+            except sa_exceptions.SQLError:
                 raise exceptions.DatabaseNotControlledError(str(self.table))
 
     def changeset(self, version=None):
--- a/kallithea/lib/dbmigrate/versions/001_initial_release.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/lib/dbmigrate/versions/001_initial_release.py	Fri Nov 27 01:39:21 2015 +0100
@@ -77,7 +77,7 @@
             session.add(self)
             session.commit()
             log.debug('updated user %s lastlogin', self.username)
-        except (DatabaseError,):
+        except DatabaseError:
             session.rollback()
 
 
--- a/kallithea/lib/helpers.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/lib/helpers.py	Fri Nov 27 01:39:21 2015 +0100
@@ -979,18 +979,17 @@
         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
+        link_attr=None,
+        curpage_attr=None,
+        dotdot_attr=None, **kwargs):
+        self.curpage_attr = curpage_attr or {'class': 'pager_curpage'}
         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
+        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):
--- a/kallithea/lib/indexers/daemon.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/lib/indexers/daemon.py	Fri Nov 27 01:39:21 2015 +0100
@@ -41,7 +41,7 @@
 project_path = dn(dn(dn(dn(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
@@ -162,6 +162,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,8 +180,7 @@
 
         node = self.get_node(repo, path, index_rev)
         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 '
--- a/kallithea/lib/middleware/simplegit.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/lib/middleware/simplegit.py	Fri Nov 27 01:39:21 2015 +0100
@@ -294,12 +294,11 @@
         if action == 'pull' and _hooks.get(Ui.HOOK_PULL):
             log_pull_action(ui=baseui, repo=_repo._repo)
 
-    def __inject_extras(self, repo_path, baseui, extras={}):
+    def __inject_extras(self, repo_path, baseui, extras=None):
         """
         Injects some extra params into baseui instance
 
         :param baseui: baseui instance
         :param extras: dict with extra params to put into baseui
         """
-
-        _set_extras(extras)
+        _set_extras(extras or {})
--- a/kallithea/lib/middleware/simplehg.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/lib/middleware/simplehg.py	Fri Nov 27 01:39:21 2015 +0100
@@ -272,7 +272,7 @@
         raise Exception('Unable to detect pull/push action !!'
                         'Are you using non standard command or client ?')
 
-    def __inject_extras(self, repo_path, baseui, extras={}):
+    def __inject_extras(self, repo_path, baseui, extras=None):
         """
         Injects some extra params into baseui instance
 
@@ -291,4 +291,4 @@
             for section in ui_sections:
                 for k, v in repoui.configitems(section):
                     baseui.setconfig(section, k, v)
-        _set_extras(extras)
+        _set_extras(extras or {})
--- a/kallithea/lib/rcmail/smtp_mailer.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/lib/rcmail/smtp_mailer.py	Fri Nov 27 01:39:21 2015 +0100
@@ -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/utils.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/lib/utils.py	Fri Nov 27 01:39:21 2015 +0100
@@ -418,6 +418,21 @@
                                                         'utf8'), 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):
     """
     Given a full path to a repository, create all nested groups that this
--- a/kallithea/lib/utils2.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/lib/utils2.py	Fri Nov 27 01:39:21 2015 +0100
@@ -78,6 +78,25 @@
     return dict(d)
 
 
+def __get_index_filenames():
+    """
+    Get list of known indexable filenames from pygment lexer internals
+    """
+    from pygments import lexers
+    from itertools import ifilter
+
+    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 str2bool(_str):
     """
     returs True/False value from given string, it tries to translate the
--- a/kallithea/lib/vcs/subprocessio.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/lib/vcs/subprocessio.py	Fri Nov 27 01:39:21 2015 +0100
@@ -156,8 +156,8 @@
     """
 
     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:
@@ -326,7 +326,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 +336,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()
--- a/kallithea/lib/vcs/utils/compat.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/lib/vcs/utils/compat.py	Fri Nov 27 01:39:21 2015 +0100
@@ -120,7 +120,8 @@
                 return cmp(type(self), type(other))
             return cmp(list(self), list(other))
 
-        def __repr__(self, _track=[]):
+        def __repr__(self, _track=None):
+            _track = _track or []
             if id(self) in _track:
                 return '...'
             _track.append(id(self))
@@ -140,8 +141,9 @@
         def __copy__(self):
             return self.__class__(self)
 
-        def __deepcopy__(self, memo={}):
+        def __deepcopy__(self, memo=None):
             from copy import deepcopy
+            memo = memo or {}
             result = self.__class__()
             memo[id(self)] = result
             result.__init__(deepcopy(tuple(self), memo))
--- a/kallithea/model/comment.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/model/comment.py	Fri Nov 27 01:39:21 2015 +0100
@@ -266,10 +266,10 @@
             q = q.filter(ChangesetComment.line_no == None)\
                 .filter(ChangesetComment.f_path == None)
 
-        if revision:
+        if revision is not None:
             q = q.filter(ChangesetComment.revision == revision)\
                 .filter(ChangesetComment.repo_id == repo_id)
-        elif pull_request:
+        elif pull_request is not None:
             pull_request = self.__get_pull_request(pull_request)
             q = q.filter(ChangesetComment.pull_request == pull_request)
         else:
--- a/kallithea/model/db.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/model/db.py	Fri Nov 27 01:39:21 2015 +0100
@@ -2103,7 +2103,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.
         """
@@ -2114,11 +2114,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
@@ -2416,11 +2412,15 @@
         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)
+        for recipient in recipients:
+            un = UserNotification()
+            un.notification = notification
+            un.user_id = recipient.user_id
+            # Mark notifications to self "pre-read" - should perhaps just be skipped
+            if recipient == created_by:
+                un.read = True
+            Session().add(un)
+
         Session().add(notification)
         Session().flush() # assign notificaiton.notification_id
         return notification
--- a/kallithea/model/forms.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/model/forms.py	Fri Nov 27 01:39:21 2015 +0100
@@ -86,7 +86,8 @@
     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 +126,9 @@
     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,8 +151,10 @@
     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
@@ -178,7 +183,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
@@ -227,8 +232,11 @@
                                                     '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
@@ -302,8 +310,11 @@
     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
@@ -421,7 +432,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
--- a/kallithea/model/notification.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/model/notification.py	Fri Nov 27 01:39:21 2015 +0100
@@ -59,7 +59,7 @@
 
     def create(self, created_by, subject, body, recipients=None,
                type_=Notification.TYPE_MESSAGE, with_email=True,
-               email_kwargs={}):
+               email_kwargs=None):
         """
 
         Creates notification of given type
@@ -75,7 +75,7 @@
         :param email_kwargs: additional dict to pass as args to email template
         """
         from kallithea.lib.celerylib import tasks, run_task
-
+        email_kwargs = email_kwargs or {}
         if recipients and not getattr(recipients, '__iter__', False):
             raise Exception('recipients must be a list or iterable')
 
--- a/kallithea/model/scm.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/model/scm.py	Fri Nov 27 01:39:21 2015 +0100
@@ -331,13 +331,13 @@
                 .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()
--- a/kallithea/model/user.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/model/user.py	Fri Nov 27 01:39:21 2015 +0100
@@ -206,9 +206,9 @@
                                    type_=Notification.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:
             raise DefaultUserException(
--- a/kallithea/model/validators.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/model/validators.py	Fri Nov 27 01:39:21 2015 +0100
@@ -89,7 +89,8 @@
     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'),
@@ -146,7 +147,8 @@
     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'),
@@ -187,7 +189,9 @@
     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'),
@@ -338,7 +342,9 @@
     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,7 +379,6 @@
             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')
@@ -483,7 +488,9 @@
     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')
@@ -699,7 +706,9 @@
     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')
--- a/kallithea/public/css/contextbar.css	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/public/css/contextbar.css	Fri Nov 27 01:39:21 2015 +0100
@@ -71,6 +71,10 @@
     vertical-align: text-bottom;
 }
 
+#content #context-bar li span {
+    margin: 0;
+}
+
 ul.horizontal-list {
     display: block;
 }
--- a/kallithea/public/css/style.css	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/public/css/style.css	Fri Nov 27 01:39:21 2015 +0100
@@ -4346,9 +4346,6 @@
     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;
@@ -4557,6 +4554,19 @@
     overflow: hidden;
 }
 
+.compare-revision-selector {
+    font-weight: bold;
+    font-size: 14px;
+}
+.compare-revision-selector > div {
+    display: inline-block;
+    margin-left: 8px;
+    vertical-align: middle;
+}
+.compare-revision-selector .btn {
+    margin-bottom: 0;
+}
+
 
 div.diffblock {
     overflow: auto;
@@ -4572,6 +4582,7 @@
 div.diffblock.margined {
     margin: 0px 20px 0px 20px;
 }
+.compare-revision-selector,
 div.diffblock .code-header {
     border-bottom: 1px solid #CCCCCC;
     background: #EEEEEE;
--- a/kallithea/public/js/base.js	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/public/js/base.js	Fri Nov 27 01:39:21 2015 +0100
@@ -377,15 +377,16 @@
         ;
 };
 
-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) {
@@ -913,14 +914,14 @@
             $('#editor_container').show();
             $('#upload_file_container').hide();
             $('#filename_container').show();
-            $('#set_mode_header').show();
+            $('#mimetype_header').show();
         });
 
     $('#upload_file_enable').click(function(){
             $('#editor_container').hide();
             $('#upload_file_container').show();
             $('#filename_container').hide();
-            $('#set_mode_header').hide();
+            $('#mimetype_header').hide();
         });
 
     return myCodeMirror
@@ -1859,8 +1860,39 @@
             }
 
             // 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;
+            }
+            if (bPos === 0 && aPos !== 0) {
+                return 1;
+            }
+
+            // Default sorting
+            if (a.text > b.text) {
+                return 1;
+            }
+            if (a.text < b.text) {
+                return -1;
+            }
+            return 0;
+        });
+    }
+    return results;
+};
+
+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.toLowerCase().indexOf(query.term.toLowerCase()),
+                bPos = b.text.toLowerCase().indexOf(query.term.toLowerCase());
             if (aPos === 0 && bPos !== 0) {
                 return -1;
             }
--- a/kallithea/templates/about.html	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/templates/about.html	Fri Nov 27 01:39:21 2015 +0100
@@ -49,6 +49,7 @@
   <li>Copyright &copy; 2015, Étienne Gilli</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>
--- a/kallithea/templates/admin/gists/edit.html	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/templates/admin/gists/edit.html	Fri Nov 27 01:39:21 2015 +0100
@@ -81,62 +81,63 @@
 
                 ## 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("editor_${h.FID('f',file.path)}", "${request.script_name}", '');
+
+                        //inject new modes
+                        var $mimetype_select = $('#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 = $('#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("${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
--- a/kallithea/templates/admin/gists/new.html	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/templates/admin/gists/new.html	Fri Nov 27 01:39:21 2015 +0100
@@ -59,51 +59,53 @@
             </div>
           ${h.end_form()}
           <script type="text/javascript">
-            var myCodeMirror = initCodeMirror('editor', "${request.script_name}", '');
+            $(document).ready(function(){
+                var myCodeMirror = initCodeMirror('editor', "${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/repos/repo_edit_caches.html	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/templates/admin/repos/repo_edit_caches.html	Fri Nov 27 01:39:21 2015 +0100
@@ -1,7 +1,7 @@
 ${h.form(url('edit_repo_caches', repo_name=c.repo_name), method='put')}
 <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.')+"');")}
+       ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate Repository Cache'),class_="btn btn-small")}
       <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.')}
--- a/kallithea/templates/base/base.html	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/templates/base/base.html	Fri Nov 27 01:39:21 2015 +0100
@@ -127,20 +127,17 @@
       </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>
+        <li ${is_current('summary')} 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 ${is_current('changelog')} 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 ${is_current('changelog')} 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 ${is_current('files')} 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 ${is_current('switch-to')} data-context="switch-to">
+          <input id="branch_switcher" name="branch_switcher" type="hidden">
         </li>
-        <li ${is_current('options')}>
+        <li ${is_current('options')} data-context="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>
              %else:
@@ -179,7 +176,7 @@
               %endif
              </ul>
         </li>
-        <li ${is_current('showpullrequest')}>
+        <li ${is_current('showpullrequest')} 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>${c.repository_pull_requests}</span>
@@ -189,15 +186,102 @@
       </ul>
   </div>
   <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 = {};
+
+      $("#branch_switcher").select2({
+          placeholder: '<i class="icon-exchange"></i> ${_('Switch To')}',
+          dropdownAutoWidth: true,
+          sortResults: prefixFirstSort,
+          formatResult: function(obj) {
+              return obj.text;
+          },
+          formatSelection: function(obj) {
+              return obj.text;
+          },
+          formatNoMatches: function(term) {
+              return "${_('No matches found')}";
+          },
+          escapeMarkup: function(m) {
+              // don't escape our custom placeholder
+              if (m.substr(0, 29) == '<i class="icon-exchange"></i>') {
+                  return m;
+              }
+
+              return Select2.util.escapeMarkup(m);
+          },
+          containerCssClass: "repo-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': '${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 .current').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': '${c.repo_name}',
+                  'branch': e.choice.text
+              });
+          }
+      });
+    });
   </script>
   <!--- END CONTEXT BAR -->
 </%def>
@@ -353,95 +437,98 @@
     </li>
 
     <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 = "${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
                 }
-                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="repotag">hg</span> ';
+                    }
+                    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;
+            }
 
-                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});
+            $("#repo_switcher").select2({
+                placeholder: '<i class="icon-database"></i> ${_('Repositories')}',
+                dropdownAutoWidth: true,
+                sortResults: prefixFirstSort,
+                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;
                     }
 
-                });
-                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});
-                    }
-                  });
-              }
-            }
-        });
+                    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});
+                        }
 
-        $("#repo_switcher").on('select2-selecting', function(e){
-            e.preventDefault();
-            window.location = pyroutes.url('summary_home', {'repo_name': e.val});
+                    });
+                    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 ##
--- a/kallithea/templates/changeset/changeset.html	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/templates/changeset/changeset.html	Fri Nov 27 01:39:21 2015 +0100
@@ -217,10 +217,8 @@
               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();
               }
           });
 
--- a/kallithea/templates/changeset/diff_block.html	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/templates/changeset/diff_block.html	Fri Nov 27 01:39:21 2015 +0100
@@ -77,19 +77,19 @@
                   ## 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>
+                    <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_ref(c.cs_ref_type, 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>
+                    <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_ref(c.a_ref_type, 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>
+                    <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_ref(c.cs_ref_type, 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>
+                    <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_ref(c.a_ref_type, 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>
+                    <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_ref(c.a_ref_type, 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>
+                    <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_ref(c.cs_ref_type, c.cs_ref_name)}</a>
                   %else:
                     ${op}???
                   %endif
--- a/kallithea/templates/compare/compare_diff.html	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/templates/compare/compare_diff.html	Fri Nov 27 01:39:21 2015 +0100
@@ -26,9 +26,15 @@
     </div>
     <div class="table">
         <div id="body" class="diffblock">
-            <div class="code-header">
+            <div class="compare-revision-selector">
+                ## divs are "inline-block" and cannot have whitespace between them.
                 <div>
-                    ${h.hidden('compare_org')} <i class="icon-right"></i> ${h.hidden('compare_other')}
+                    ${h.hidden('compare_org')}
+                </div><div>
+                    <i class="icon-right"></i>
+                </div><div>
+                    ${h.hidden('compare_other')}
+                </div><div>
                     %if not c.compare_home:
                         <a class="btn btn-small" href="${c.swap_url}"><i class="icon-arrows-cw"></i> ${_('Swap')}</a>
                     %endif
--- a/kallithea/templates/files/files_add.html	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/templates/files/files_add.html	Fri Nov 27 01:39:21 2015 +0100
@@ -44,14 +44,14 @@
                   ${_('or')} <div class="btn btn-small" id="upload_file_enable">${_('Upload File')}</div>
               </span>
               <span id="upload_file_container" class="reviewer_ac" style="display:none">
-                  <input type="file"  size="20" name="upload_file" id="upload_file">
+                  <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"/>
+            <div class="code-header" id="mimetype_header">
+                <label class="commit" for="mimetype">${_('New file type')}</label>
+                <select id="mimetype" name="mimetype"/>
             </div>
                 <div id="editor_container">
                     <pre id="editor_pre"></pre>
@@ -66,50 +66,53 @@
             </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.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);
 
-            //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_edit.html	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/templates/files/files_edit.html	Fri Nov 27 01:39:21 2015 +0100
@@ -55,8 +55,8 @@
                       % endif
                     </div>
                 </div>
-                <label class="commit" for="set_mode">${_('Editing file')}: ${c.file.unicode_path}</label>
-                <select id="set_mode" name="set_mode"/>
+                <label class="commit" for="mimetype">${_('Editing file')}: ${c.file.unicode_path}</label>
+                <select id="mimetype" name="mimetype"/>
             </div>
                 <pre id="editor_pre"></pre>
                 <textarea id="editor" name="content" style="display:none">${h.escape(c.file.content)|n}</textarea>
@@ -73,40 +73,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.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);
 
-   //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("${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_source.html	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/templates/files/files_source.html	Fri Nov 27 01:39:21 2015 +0100
@@ -63,15 +63,21 @@
           ${_('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.file_changeset.raw_id,f_path=c.f_path))}
+            %endif
+            </h4>
         %endif
       %endif
     </div>
--- a/kallithea/tests/__init__.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/tests/__init__.py	Fri Nov 27 01:39:21 2015 +0100
@@ -220,7 +220,6 @@
         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
--- a/kallithea/tests/models/test_notifications.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/tests/models/test_notifications.py	Fri Nov 27 01:39:21 2015 +0100
@@ -144,7 +144,7 @@
         Session().commit()
 
         self.assertEqual(NotificationModel()
-                         .get_unread_cnt_for_user(self.u1), 1)
+                         .get_unread_cnt_for_user(self.u1), 0)
         self.assertEqual(NotificationModel()
                          .get_unread_cnt_for_user(self.u2), 0)
         self.assertEqual(NotificationModel()
@@ -156,7 +156,7 @@
         Session().commit()
 
         self.assertEqual(NotificationModel()
-                         .get_unread_cnt_for_user(self.u1), 2)
+                         .get_unread_cnt_for_user(self.u1), 0)
         self.assertEqual(NotificationModel()
                          .get_unread_cnt_for_user(self.u2), 1)
         self.assertEqual(NotificationModel()
--- a/kallithea/tests/other/manual_test_vcs_operations.py	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/tests/other/manual_test_vcs_operations.py	Fri Nov 27 01:39:21 2015 +0100
@@ -273,8 +273,8 @@
         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)
+                                               ==HG_REPO).all()
+        self.assertEqual(key, [])
 
     def test_push_invalidates_cache_git(self):
         key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
@@ -295,9 +295,8 @@
         _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)
+                                               ==GIT_REPO).all()
+        self.assertEqual(key, [])
 
     def test_push_wrong_credentials_hg(self):
         DEST = _get_tmp_dir()
--- a/kallithea/tests/test.ini	Fri Nov 27 01:34:47 2015 +0100
+++ b/kallithea/tests/test.ini	Fri Nov 27 01:39:21 2015 +0100
@@ -163,6 +163,7 @@
 
 ## COMMON ##
 host = 127.0.0.1
+#port = 5000
 port = 4999
 
 ## middleware for hosting the WSGI application under a URL prefix
@@ -224,6 +225,7 @@
 
 ## options for showing and identifying changesets
 show_sha_length = 12
+#show_revision_number = false
 show_revision_number = true
 
 ## gist URL alias, used to create nicer urls for gist. This should be an
@@ -280,12 +282,6 @@
 #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
@@ -301,6 +297,18 @@
 ## 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        ####
 ####################################
@@ -347,6 +355,7 @@
 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.expire = 1
 beaker.cache.sql_cache_short.key_length = 256
 
@@ -543,8 +552,8 @@
 
 [logger_sqlalchemy]
 #level = INFO
+level = ERROR
 #handlers = console_sql
-level = ERROR
 handlers = console
 qualname = sqlalchemy.engine
 propagate = 0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/docs-headings.py	Fri Nov 27 01:39:21 2015 +0100
@@ -0,0 +1,79 @@
+#!/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():
+    for fn in subprocess.check_output(['hg', 'loc', 'set:**.rst+kallithea/i18n/how_to']).splitlines():
+        print 'processing %s:' % fn
+        s = file(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)
+
+        file(fn, 'w').write(s)
+        print subprocess.check_output(['hg', 'diff', fn])
+        print
+
+if __name__ == '__main__':
+    main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/generate-ini.py	Fri Nov 27 01:39:21 2015 +0100
@@ -0,0 +1,173 @@
+#!/usr/bin/env python2
+"""
+Based on kallithea/bin/template.ini.mako, generate
+  kallithea/config/deployment.ini_tmpl
+  development.ini
+  kallithea/tests/test.ini
+"""
+
+import re
+
+makofile = 'kallithea/bin/template.ini.mako'
+
+# the mako conditionals used in all other ini files and templates
+selected_mako_conditionals = set([
+    "database_engine == 'sqlite'",
+    "http_server == 'waitress'",
+    "error_aggregation_service == 'errormator'",
+    "error_aggregation_service == 'sentry'",
+])
+
+# the mako variables used in all other ini files and templates
+mako_variable_values = {
+    'host': '127.0.0.1',
+    'port': '5000',
+    'here': '%(here)s',
+    'uuid()': '${app_instance_uuid}',
+}
+
+# files to be generated from the mako template
+ini_files = [
+    ('kallithea/config/deployment.ini_tmpl',
+        '''
+        Kallithea - Example config
+
+        The %(here)s variable will be replaced with the parent directory of this file
+        ''',
+        {}, # exactly the same settings as template.ini.mako
+    ),
+    ('kallithea/tests/test.ini',
+        '''
+        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
+        ''',
+        {
+            '[server:main]': {
+                'port': '4999',
+            },
+            '[app:main]': {
+                'initial_repo_scan': 'true',
+                'app_instance_uuid': 'test',
+                'vcs_full_cache': 'false',
+                'show_revision_number': 'true',
+                'beaker.cache.sql_cache_short.expire': '1',
+                'beaker.session.secret': '{74e0cd75-b339-478b-b129-07dd221def1f}',
+                'sqlalchemy.db1.url': 'sqlite:///%(here)s/kallithea_test.sqlite',
+            },
+            '[logger_root]': {
+                'level': 'DEBUG',
+            },
+            '[logger_sqlalchemy]': {
+                'level': 'ERROR',
+                'handlers': 'console',
+            },
+            '[handler_console]': {
+                'level': 'NOTSET',
+            },
+        },
+    ),
+    ('development.ini',
+        '''
+        Kallithea - Development config:
+        listening on *:5000
+        sqlite and kallithea.db
+        initial_repo_scan = true
+        set debug = true
+        verbose and colorful logging
+
+        The %(here)s variable will be replaced with the parent directory of this file
+        ''',
+        {
+            '[server:main]': {
+                'host': '0.0.0.0',
+            },
+            '[app:main]': {
+                'initial_repo_scan': 'true',
+                'set debug': 'true',
+                'app_instance_uuid': 'development-not-secret',
+                'beaker.session.secret': 'development-not-secret',
+            },
+            '[handler_console]': {
+                'level': 'DEBUG',
+                'formatter': 'color_formatter',
+            },
+            '[handler_console_sql]': {
+                'level': 'DEBUG',
+                'formatter': 'color_formatter_sql',
+            },
+        },
+    ),
+]
+
+
+def main():
+    # make sure all mako lines starting with '#' (the '##' comments) are marked up as <text>
+    print 'reading:', makofile
+    mako_org = file(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
+        file(makofile, 'w').write(mako_marked_up)
+
+    # select the right mako conditionals for the other less sophisticated formats
+    def sub_conditionals(m):
+        """given a %if...%endif match, replace with just the selected
+        conditional sections enabled and the rest as comments
+        """
+        conditional_lines = m.group(1)
+        def sub_conditional(m):
+            """given a conditional and the corresponding lines, return them raw
+            or commented out, based on whether conditional is selected
+            """
+            criteria, lines = m.groups()
+            if criteria not in selected_mako_conditionals:
+                lines = '\n'.join((l if not l or l.startswith('#') else '#' + l) for l in lines.split('\n'))
+            return lines
+        conditional_lines = re.sub(r'^%(?:el)?if (.*):\n((?:^[^%\n].*\n|\n)*)',
+            sub_conditional, conditional_lines, flags=re.MULTILINE)
+        return conditional_lines
+    mako_no_conditionals = re.sub(r'^(%if .*\n(?:[^%\n].*\n|%elif .*\n|\n)*)%endif\n',
+        sub_conditionals, mako_no_text_markup, flags=re.MULTILINE)
+
+    # expand mako variables
+    def pyrepl(m):
+        return mako_variable_values.get(m.group(1), m.group(0))
+    mako_no_variables = re.sub(r'\${([^}]*)}', pyrepl, mako_no_conditionals)
+
+    # remove utf-8 coding header
+    base_ini = re.sub(r'^## -\*- coding: utf-8 -\*-\n', '', mako_no_variables)
+
+    # create ini files
+    for fn, desc, settings in ini_files:
+        print 'updating:', fn
+        ini_lines = re.sub(
+            '# Kallithea - config file generated with kallithea-config *#\n',
+            ''.join('# %-77s#\n' % l.strip() for l in desc.strip().split('\n')),
+            base_ini)
+        def process_section(m):
+            """process a ini section, replacing values as necessary"""
+            sectionname, lines = m.groups()
+            if sectionname in settings:
+                section_settings = settings[sectionname]
+                def process_line(m):
+                    """process a section line and update value if necessary"""
+                    setting, value = m.groups()
+                    line = m.group(0)
+                    if setting in section_settings:
+                        line = '%s = %s' % (setting, section_settings[setting])
+                        if '$' not in value:
+                            line = '#%s = %s\n%s' % (setting, value, line)
+                    return line.rstrip()
+                lines = re.sub(r'^([^#\n].*) = ?(.*)', process_line, lines, flags=re.MULTILINE)
+            return sectionname + '\n' + lines
+        ini_lines = re.sub(r'^(\[.*\])\n((?:(?:[^[\n].*)?\n)*)', process_section, ini_lines, flags=re.MULTILINE)
+        file(fn, 'w').write(ini_lines)
+
+if __name__ == '__main__':
+    main()