changeset 6532:33b71a130b16

templates: properly escape inline JavaScript values TLDR: Kallithea has issues with escaping values for use in inline JS. Despite judicious poking of the code, no actual security vulnerabilities have been found, just lots of corner-case bugs. This patch fixes those, and hardens the code against actual security issues. The long version: To embed a Python value (typically a 'unicode' plain-text value) in a larger file, it must be escaped in a context specific manner. Example: >>> s = u'<script>alert("It\'s a trap!");</script>' 1) Escaped for insertion into HTML element context >>> print cgi.escape(s) &lt;script&gt;alert("It's a trap!");&lt;/script&gt; 2) Escaped for insertion into HTML element or attribute context >>> print h.escape(s) &lt;script&gt;alert(&#34;It&#39;s a trap!&#34;);&lt;/script&gt; This is the default Mako escaping, as usually used by Kallithea. 3) Encoded as JSON >>> print json.dumps(s) "<script>alert(\"It's a trap!\");</script>" 4) Escaped for insertion into a JavaScript file >>> print '(' + json.dumps(s) + ')' ("<script>alert(\"It's a trap!\");</script>") The parentheses are not actually required for strings, but may be needed to avoid syntax errors if the value is a number or dict (object). 5) Escaped for insertion into a HTML inline <script> element >>> print h.js(s) ("\x3cscript\x3ealert(\"It's a trap!\");\x3c/script\x3e") Here, we need to combine JS and HTML escaping, further complicated by the fact that "<script>" tag contents can either be parsed in XHTML mode (in which case '<', '>' and '&' must additionally be XML escaped) or HTML mode (in which case '</script>' must be escaped, but not using HTML escaping, which is not available in HTML "<script>" tags). Therefore, the XML special characters (which can only occur in string literals) are escaped using JavaScript string literal escape sequences. (This, incidentally, is why modern web security best practices ban all use of inline JavaScript...) Unsurprisingly, Kallithea does not do (5) correctly. In most cases, Kallithea might slap a pair of single quotes around the HTML escaped Python value. A typical benign example: $('#child_link').html('${_('No revisions')}'); This works in English, but if a localized version of the string contains an apostrophe, the result will be broken JavaScript. In the more severe cases, where the text is user controllable, it leaves the door open to injections. In this example, the script inserts the string as HTML, so Mako's implicit HTML escaping makes sense; but in many other cases, HTML escaping is actually an error, because the value is not used by the script in an HTML context. The good news is that the HTML escaping thwarts attempts at XSS, since it's impossible to inject syntactically valid JavaScript of any useful complexity. It does allow JavaScript errors and gibberish to appear on the page, though. In these cases, the escaping has been fixed to use either the new 'h.js' helper, which does JavaScript escaping (but not HTML escaping), OR the new 'h.jshtml' helper (which does both), in those cases where it was unclear if the value might be used (by the script) in an HTML context. Some of these can probably be "relaxed" from h.jshtml to h.js later, but for now, using h.jshtml fixes escaping and doesn't introduce new errors. In a few places, Kallithea JSON encodes values in the controller, then inserts the JSON (without any further escaping) into <script> tags. This is also wrong, and carries actual risk of XSS vulnerabilities. However, in all cases, security vulnerabilities were narrowly avoided due to other filtering in Kallithea. (E.g. many special characters are banned from appearing in usernames.) In these cases, the escaping has been fixed and moved to the template, making it immediately visible that proper escaping has been performed. Mini-FAQ (frequently anticipated questions): Q: Why do everything in one big, hard to review patch? Q: Why add escaping in specific case FOO, it doesn't seem needed? Because the goal here is to have "escape everywhere" as the default policy, rather than identifying individual bugs and fixing them one by one by adding escaping where needed. As such, this patch surely introduces a lot of needless escaping. This is no different from how Mako/Pylons HTML escape everything by default, even when not needed: it's errs on the side of needless work, to prevent erring on the side of skipping required (and security critical) work. As for reviewability, the most important thing to notice is not where escaping has been introduced, but any places where it might have been missed (or where h.jshtml is needed, but h.js is used). Q: The added escaping is kinda verbose/ugly. That is not a question, but yes, I agree. Hopefully it'll encourage us to move away from inline JavaScript altogether. That's a significantly larger job, though; with luck this patch will keep us safe and secure until such a time as we can implement the real fix. Q: Why not use Mako filter syntax ("${val|h.js}")? Because of long-standing Mako bug #140, preventing use of 'h' in filters. Q: Why not work around bug #140, or even use straight "${val|js}"? Because Mako still applies the default h.escape filter before the explicitly specified filters. Q: Where do we go from here? Longer term, we should stop doing variable expansions in script blocks, and instead pass data to JS via e.g. data attributes, or asynchronously using AJAX calls. Once we've done that, we can remove inline JavaScript altogether in favor of separate script files, and set a strict Content Security Policy explicitly blocking inline scripting, and thus also the most common kind of cross-site scripting attack.
author Søren Løvborg <sorenl@unity3d.com>
date Tue, 28 Feb 2017 17:19:00 +0100
parents 482c531e62ef
children 5d60c9a391cd
files kallithea/controllers/admin/my_account.py kallithea/controllers/admin/repo_groups.py kallithea/controllers/admin/repos.py kallithea/controllers/admin/user_groups.py kallithea/controllers/admin/users.py kallithea/controllers/changelog.py kallithea/controllers/changeset.py kallithea/controllers/compare.py kallithea/controllers/home.py kallithea/controllers/journal.py kallithea/controllers/pullrequests.py kallithea/controllers/summary.py kallithea/lib/helpers.py kallithea/model/repo.py kallithea/templates/admin/admin.html kallithea/templates/admin/gists/edit.html kallithea/templates/admin/gists/new.html kallithea/templates/admin/my_account/my_account_repos.html kallithea/templates/admin/my_account/my_account_watched.html kallithea/templates/admin/notifications/notifications.html kallithea/templates/admin/notifications/show_notification.html kallithea/templates/admin/repo_groups/repo_group_edit_perms.html kallithea/templates/admin/repo_groups/repo_groups.html kallithea/templates/admin/repos/repo_creating.html kallithea/templates/admin/repos/repo_edit_permissions.html kallithea/templates/admin/repos/repo_edit_settings.html kallithea/templates/admin/repos/repos.html kallithea/templates/admin/settings/settings_hooks.html kallithea/templates/admin/settings/settings_system.html kallithea/templates/admin/user_groups/user_group_edit_perms.html kallithea/templates/admin/user_groups/user_groups.html kallithea/templates/admin/users/users.html kallithea/templates/base/base.html kallithea/templates/base/root.html kallithea/templates/changelog/changelog.html kallithea/templates/changeset/changeset.html kallithea/templates/compare/compare_cs.html kallithea/templates/compare/compare_diff.html kallithea/templates/files/diff_2way.html kallithea/templates/files/files.html kallithea/templates/files/files_add.html kallithea/templates/files/files_browser.html kallithea/templates/files/files_edit.html kallithea/templates/files/files_source.html kallithea/templates/index_base.html kallithea/templates/journal/journal.html kallithea/templates/password_reset.html kallithea/templates/pullrequests/pullrequest.html kallithea/templates/pullrequests/pullrequest_show.html kallithea/templates/register.html kallithea/templates/summary/statistics.html kallithea/templates/summary/summary.html kallithea/tests/functional/test_changelog.py kallithea/tests/functional/test_home.py kallithea/tests/functional/test_repo_groups.py kallithea/tests/functional/test_summary.py
diffstat 56 files changed, 304 insertions(+), 275 deletions(-) [+]
line wrap: on
line diff
--- a/kallithea/controllers/admin/my_account.py	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/controllers/admin/my_account.py	Tue Feb 28 17:19:00 2017 +0100
@@ -41,7 +41,6 @@
 from kallithea.lib.auth import LoginRequired, NotAnonymous, AuthUser
 from kallithea.lib.base import BaseController, render
 from kallithea.lib.utils2 import generate_api_key, safe_int
-from kallithea.lib.compat import json
 from kallithea.model.db import Repository, UserEmailMap, User, UserFollowing
 from kallithea.model.forms import UserForm, PasswordChangeForm
 from kallithea.model.user import UserModel
@@ -84,10 +83,8 @@
                          .filter(Repository.owner_id ==
                                  request.authuser.user_id).all()
 
-        repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list,
+        return RepoModel().get_repos_as_dict(repos_list=repos_list,
                                                    admin=admin)
-        #json used to render the grid
-        return json.dumps(repos_data)
 
     def my_account(self):
         c.active = 'profile'
@@ -176,7 +173,7 @@
         c.active = 'repos'
         self.__load_data()
 
-        #json used to render the grid
+        #data used to render the grid
         c.data = self._load_my_repos_data()
         return render('admin/my_account/my_account.html')
 
@@ -184,7 +181,7 @@
         c.active = 'watched'
         self.__load_data()
 
-        #json used to render the grid
+        #data used to render the grid
         c.data = self._load_my_repos_data(watched=True)
         return render('admin/my_account/my_account.html')
 
--- a/kallithea/controllers/admin/repo_groups.py	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/controllers/admin/repo_groups.py	Tue Feb 28 17:19:00 2017 +0100
@@ -39,7 +39,6 @@
 import kallithea
 from kallithea.config.routing import url
 from kallithea.lib import helpers as h
-from kallithea.lib.compat import json
 from kallithea.lib.auth import LoginRequired, \
     HasRepoGroupPermissionLevelDecorator, HasRepoGroupPermissionLevel, \
     HasPermissionAny
@@ -141,13 +140,13 @@
                                              repo_count)
             })
 
-        c.data = json.dumps({
+        c.data = {
             "totalRecords": total_records,
             "startIndex": 0,
             "sort": None,
             "dir": "asc",
             "records": repo_groups_data
-        })
+        }
 
         return render('admin/repo_groups/repo_groups.html')
 
@@ -304,8 +303,8 @@
         repos_list = Repository.query(sorted=True).filter_by(group=c.group).all()
         repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list,
                                                    admin=False, short_name=True)
-        #json used to render the grid
-        c.data = json.dumps(repos_data)
+        # data used to render the grid
+        c.data = repos_data
 
         return render('admin/repo_groups/repo_group_show.html')
 
--- a/kallithea/controllers/admin/repos.py	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/controllers/admin/repos.py	Tue Feb 28 17:19:00 2017 +0100
@@ -47,7 +47,6 @@
 from kallithea.model.forms import RepoForm, RepoFieldForm, RepoPermsForm
 from kallithea.model.scm import ScmModel, AvailableRepoGroupChoices, RepoList
 from kallithea.model.repo import RepoModel
-from kallithea.lib.compat import json
 from kallithea.lib.exceptions import AttachedForksError
 from kallithea.lib.utils2 import safe_int
 
@@ -105,8 +104,8 @@
         repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
                                                    admin=True,
                                                    super_user_actions=True)
-        #json used to render the grid
-        c.data = json.dumps(repos_data)
+        #data used to render the grid
+        c.data = repos_data
 
         return render('admin/repos/repos.html')
 
--- a/kallithea/controllers/admin/user_groups.py	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/controllers/admin/user_groups.py	Tue Feb 28 17:19:00 2017 +0100
@@ -56,7 +56,6 @@
     CustomDefaultPermissionsForm
 from kallithea.model.meta import Session
 from kallithea.lib.utils import action_logger
-from kallithea.lib.compat import json
 
 log = logging.getLogger(__name__)
 
@@ -119,13 +118,13 @@
                 "action": user_group_actions(user_gr.users_group_id, user_gr.users_group_name)
             })
 
-        c.data = json.dumps({
+        c.data = {
             "totalRecords": total_records,
             "startIndex": 0,
             "sort": None,
             "dir": "asc",
             "records": user_groups_data
-        })
+        }
 
         return render('admin/user_groups/user_groups.html')
 
--- a/kallithea/controllers/admin/users.py	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/controllers/admin/users.py	Tue Feb 28 17:19:00 2017 +0100
@@ -51,7 +51,6 @@
 from kallithea.model.user import UserModel
 from kallithea.model.meta import Session
 from kallithea.lib.utils import action_logger
-from kallithea.lib.compat import json
 from kallithea.lib.utils2 import datetime_to_time, safe_int, generate_api_key
 
 log = logging.getLogger(__name__)
@@ -103,13 +102,13 @@
                 "action": user_actions(user.user_id, user.username),
             })
 
-        c.data = json.dumps({
+        c.data = {
             "totalRecords": total_records,
             "startIndex": 0,
             "sort": None,
             "dir": "asc",
             "records": users_data
-        })
+        }
 
         return render('admin/users/users.html')
 
--- a/kallithea/controllers/changelog.py	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/controllers/changelog.py	Tue Feb 28 17:19:00 2017 +0100
@@ -36,7 +36,6 @@
 from kallithea.config.routing import url
 from kallithea.lib.auth import LoginRequired, HasRepoPermissionLevelDecorator
 from kallithea.lib.base import BaseRepoController, render
-from kallithea.lib.compat import json
 from kallithea.lib.graphmod import graph_data
 from kallithea.lib.page import RepoPage
 from kallithea.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \
@@ -171,7 +170,7 @@
         revs = []
         if not f_path:
             revs = [x.revision for x in c.pagination]
-        c.jsdata = json.dumps(graph_data(c.db_repo_scm_instance, revs))
+        c.jsdata = graph_data(c.db_repo_scm_instance, revs)
 
         c.revision = revision # requested revision ref
         c.first_revision = c.pagination[0] # pagination is never empty here!
--- a/kallithea/controllers/changeset.py	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/controllers/changeset.py	Tue Feb 28 17:19:00 2017 +0100
@@ -36,7 +36,6 @@
 from kallithea.lib.vcs.exceptions import RepositoryError, \
     ChangesetDoesNotExistError, EmptyRepositoryError
 
-from kallithea.lib.compat import json
 import kallithea.lib.helpers as h
 from kallithea.lib.auth import LoginRequired, HasRepoPermissionLevelDecorator, \
     NotAnonymous
@@ -333,7 +332,7 @@
                 c.cs_ranges_org = None
                 c.cs_comments = {}
                 revs = [ctx.revision for ctx in reversed(c.cs_ranges)]
-                c.jsdata = json.dumps(graph_data(c.db_repo_scm_instance, revs))
+                c.jsdata = graph_data(c.db_repo_scm_instance, revs)
                 return render('changeset/changeset_range.html')
 
     @LoginRequired()
--- a/kallithea/controllers/compare.py	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/controllers/compare.py	Tue Feb 28 17:19:00 2017 +0100
@@ -45,7 +45,6 @@
 from kallithea.lib.diffs import LimitedDiffContainer
 from kallithea.controllers.changeset import _ignorews_url, _context_url
 from kallithea.lib.graphmod import graph_data
-from kallithea.lib.compat import json, OrderedDict
 
 log = logging.getLogger(__name__)
 
@@ -227,7 +226,7 @@
         c.statuses = c.cs_repo.statuses(raw_ids)
 
         revs = [ctx.revision for ctx in reversed(c.cs_ranges)]
-        c.jsdata = json.dumps(graph_data(c.cs_repo.scm_instance, revs))
+        c.jsdata = graph_data(c.cs_repo.scm_instance, revs)
 
         if partial:
             return render('compare/compare_cs.html')
--- a/kallithea/controllers/home.py	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/controllers/home.py	Tue Feb 28 17:19:00 2017 +0100
@@ -34,7 +34,6 @@
 from sqlalchemy.sql.expression import func
 
 from kallithea.lib.utils import conditional_cache
-from kallithea.lib.compat import json
 from kallithea.lib.auth import LoginRequired, HasRepoPermissionLevelDecorator
 from kallithea.lib.base import BaseController, render, jsonify
 from kallithea.model.db import Repository, RepoGroup
@@ -61,8 +60,8 @@
 
         repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list,
                                                    admin=False, short_name=True)
-        #json used to render the grid
-        c.data = json.dumps(repos_data)
+        #data used to render the grid
+        c.data = repos_data
 
         return render('/index.html')
 
--- a/kallithea/controllers/journal.py	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/controllers/journal.py	Tue Feb 28 17:19:00 2017 +0100
@@ -48,7 +48,6 @@
 import kallithea.lib.helpers as h
 from kallithea.lib.auth import LoginRequired, NotAnonymous
 from kallithea.lib.base import BaseController, render
-from kallithea.lib.compat import json
 from kallithea.lib.page import Page
 from kallithea.lib.utils2 import safe_int, AttributeDict
 
@@ -218,8 +217,8 @@
 
         repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list,
                                                    admin=True)
-        #json used to render the grid
-        c.data = json.dumps(repos_data)
+        #data used to render the grid
+        c.data = repos_data
 
         return render('journal/journal.html')
 
--- a/kallithea/controllers/pullrequests.py	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/controllers/pullrequests.py	Tue Feb 28 17:19:00 2017 +0100
@@ -40,7 +40,6 @@
 from kallithea.lib.auth import LoginRequired, HasRepoPermissionLevelDecorator, \
     NotAnonymous
 from kallithea.lib.base import BaseRepoController, render, jsonify
-from kallithea.lib.compat import json, OrderedDict
 from kallithea.lib.diffs import LimitedDiffContainer
 from kallithea.lib.page import Page
 from kallithea.lib.utils import action_logger
@@ -619,7 +618,7 @@
                 'error')
         c.cs_ranges_org = None # not stored and not important and moving target - could be calculated ...
         revs = [ctx.revision for ctx in reversed(c.cs_ranges)]
-        c.jsdata = json.dumps(graph_data(org_scm_instance, revs))
+        c.jsdata = graph_data(org_scm_instance, revs)
 
         c.is_range = False
         try:
@@ -701,7 +700,7 @@
 
         c.avail_revs = avail_revs
         c.avail_cs = [org_scm_instance.get_changeset(r) for r in avail_show]
-        c.avail_jsdata = json.dumps(graph_data(org_scm_instance, avail_show))
+        c.avail_jsdata = graph_data(org_scm_instance, avail_show)
 
         raw_ids = [x.raw_id for x in c.cs_ranges]
         c.cs_comments = c.cs_repo.get_comments(raw_ids)
--- a/kallithea/controllers/summary.py	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/controllers/summary.py	Tue Feb 28 17:19:00 2017 +0100
@@ -146,12 +146,12 @@
                                "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
                           for x, y in lang_stats_d.items())
 
-            c.trending_languages = json.dumps(
+            c.trending_languages = (
                 sorted(lang_stats, reverse=True, key=lambda k: k[1])[:10]
             )
         else:
             c.no_data = True
-            c.trending_languages = json.dumps([])
+            c.trending_languages = []
 
         c.enable_downloads = c.db_repo.enable_downloads
         c.readme_data, c.readme_file = \
@@ -202,7 +202,7 @@
                                "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
                           for x, y in lang_stats_d.items())
 
-            c.trending_languages = json.dumps(
+            c.trending_languages = (
                 sorted(lang_stats, reverse=True, key=lambda k: k[1])[:10]
             )
             last_rev = stats.stat_on_revision + 1
@@ -214,9 +214,9 @@
                 c.stats_percentage = '%.2f' % ((float((last_rev)) /
                                                 c.repo_last_rev) * 100)
         else:
-            c.commit_data = json.dumps({})
-            c.overview_data = json.dumps([[ts_min_y, 0], [ts_max_y, 10]])
-            c.trending_languages = json.dumps({})
+            c.commit_data = {}
+            c.overview_data = ([[ts_min_y, 0], [ts_max_y, 10]])
+            c.trending_languages = {}
             c.no_data = True
 
         recurse_limit = 500  # don't recurse more than 500 times when parsing
--- a/kallithea/lib/helpers.py	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/lib/helpers.py	Tue Feb 28 17:19:00 2017 +0100
@@ -18,6 +18,7 @@
 available to Controllers. This module is available to both as 'h'.
 """
 import hashlib
+import json
 import StringIO
 import logging
 import re
@@ -85,6 +86,50 @@
         .replace("'", "&apos;")
         )
 
+def js(value):
+    """Convert Python value to the corresponding JavaScript representation.
+
+    This is necessary to safely insert arbitrary values into HTML <script>
+    sections e.g. using Mako template expression substitution.
+
+    Note: Rather than using this function, it's preferable to avoid the
+    insertion of values into HTML <script> sections altogether. Instead,
+    data should (to the extent possible) be passed to JavaScript using
+    data attributes or AJAX calls, eliminating the need for JS specific
+    escaping.
+
+    Note: This is not safe for use in attributes (e.g. onclick), because
+    quotes are not escaped.
+
+    Because the rules for parsing <script> varies between XHTML (where
+    normal rules apply for any special characters) and HTML (where
+    entities are not interpreted, but the literal string "</script>"
+    is forbidden), the function ensures that the result never contains
+    '&', '<' and '>', thus making it safe in both those contexts (but
+    not in attributes).
+    """
+    return literal(
+        ('(' + json.dumps(value) + ')')
+        # In JSON, the following can only appear in string literals.
+        .replace('&', r'\x26')
+        .replace('<', r'\x3c')
+        .replace('>', r'\x3e')
+    )
+
+def jshtml(val):
+    """HTML escapes a string value, then converts the resulting string
+    to its corresponding JavaScript representation (see `js`).
+
+    This is used when a plain-text string (possibly containing special
+    HTML characters) will be used by a script in an HTML context (e.g.
+    element.innerHTML or jQuery's 'html' method).
+
+    If in doubt, err on the side of using `jshtml` over `js`, since it's
+    better to escape too much than too little.
+    """
+    return js(escape(val))
+
+
 def shorter(s, size=20, firstline=False, postfix='...'):
     """Truncate s to size, including the postfix string if truncating.
     If firstline, truncate at newline.
--- a/kallithea/model/repo.py	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/model/repo.py	Tue Feb 28 17:19:00 2017 +0100
@@ -35,7 +35,6 @@
 
 from kallithea.lib.utils import make_ui
 from kallithea.lib.vcs.backends import get_backend
-from kallithea.lib.compat import json
 from kallithea.lib.utils2 import LazyProperty, safe_str, safe_unicode, \
     remove_prefix, obfuscate_url_pw, get_current_authuser
 from kallithea.lib.caching_query import FromCache
@@ -127,7 +126,7 @@
             .filter(User.active == True) \
             .order_by(User.name, User.lastname) \
             .all()
-        return json.dumps([
+        return [
             {
                 'id': u.user_id,
                 'fname': h.escape(u.name),
@@ -136,7 +135,6 @@
                 'gravatar_lnk': h.gravatar_url(u.email, size=28, default='default'),
                 'gravatar_size': 14,
             } for u in users]
-        )
 
     def get_user_groups_js(self):
         user_groups = UserGroup.query() \
@@ -145,13 +143,12 @@
             .options(subqueryload(UserGroup.members)) \
             .all()
         user_groups = UserGroupList(user_groups, perm_level='read')
-        return json.dumps([
+        return [
             {
                 'id': gr.users_group_id,
                 'grname': gr.users_group_name,
                 'grmembers': len(gr.members),
             } for gr in user_groups]
-        )
 
     @classmethod
     def _render_datatable(cls, tmpl, *args, **kwargs):
--- a/kallithea/templates/admin/admin.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/admin/admin.html	Tue Feb 28 17:19:00 2017 +0100
@@ -46,7 +46,7 @@
   $('#filter_form').submit(function (e) {
       e.preventDefault();
       var val = $('#j_filter').val();
-      window.location = "${url.current(filter='__FILTER__')}".replace('__FILTER__',val);
+      window.location = ${h.js(url.current(filter='__FILTER__'))}.replace('__FILTER__',val);
   });
   fix_j_filter_width($('#j_filter').val().length);
 });
--- a/kallithea/templates/admin/gists/edit.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/admin/gists/edit.html	Tue Feb 28 17:19:00 2017 +0100
@@ -81,10 +81,10 @@
                 ## dynamic edit box.
                 <script type="text/javascript">
                     $(document).ready(function(){
-                        var myCodeMirror = initCodeMirror("editor_${h.FID('f',file.path)}", "${request.script_name}", '');
+                        var myCodeMirror = initCodeMirror(${h.js('editor_' + h.FID('f',file.path))}, ${h.jshtml(request.script_name)}, '');
 
                         //inject new modes
-                        var $mimetype_select = $('#mimetype_${h.FID('f',file.path)}');
+                        var $mimetype_select = $(${h.js('#mimetype_' + h.FID('f',file.path))});
                         $mimetype_select.each(function(){
                             var modes_select = this;
                             var index = 1;
@@ -102,7 +102,7 @@
                             }
                         });
 
-                        var $filename_input = $('#filename_${h.FID('f',file.path)}');
+                        var $filename_input = $(${h.js('#filename_' + h.FID('f',file.path))});
                         // on select change set new mode
                         $mimetype_select.change(function(e){
                             var selected = e.currentTarget;
@@ -130,7 +130,7 @@
                         });
 
                         // set mode on page load
-                        var detected_mode = CodeMirror.findModeByExtension("${file.extension}");
+                        var detected_mode = CodeMirror.findModeByExtension(${h.js(file.extension)});
 
                         if (detected_mode){
                             setCodeMirrorMode(myCodeMirror, detected_mode);
@@ -152,8 +152,8 @@
 
                   // check for newer version.
                   $.ajax({
-                    url: "${h.url('edit_gist_check_revision', gist_id=c.gist.gist_access_id)}",
-                    data: {'revision': '${c.file_changeset.raw_id}', '_authentication_token': _authentication_token},
+                    url: ${h.js(h.url('edit_gist_check_revision', gist_id=c.gist.gist_access_id))},
+                    data: {'revision': ${h.js(c.file_changeset.raw_id)}, '_authentication_token': _authentication_token},
                     dataType: 'json',
                     type: 'POST',
                     success: function(data) {
--- a/kallithea/templates/admin/gists/new.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/admin/gists/new.html	Tue Feb 28 17:19:00 2017 +0100
@@ -59,7 +59,7 @@
           ${h.end_form()}
           <script type="text/javascript">
             $(document).ready(function(){
-                var myCodeMirror = initCodeMirror('editor', "${request.script_name}", '');
+                var myCodeMirror = initCodeMirror('editor', ${h.jshtml(request.script_name)}, '');
 
                 //inject new modes
                 var $mimetype_select = $('#mimetype');
--- a/kallithea/templates/admin/my_account/my_account_repos.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/admin/my_account/my_account_repos.html	Tue Feb 28 17:19:00 2017 +0100
@@ -5,15 +5,15 @@
 </div>
 
 <script>
-  var data = ${c.data|n};
+  var data = ${h.js(c.data)};
   var myDataTable = $("#datatable_list_wrap").DataTable({
         data: data.records,
         columns: [
             {data: "raw_name", "visible": false, searchable: false},
-            {data: "name", "orderData": 1, title: "${_('Name')}"},
+            {data: "name", "orderData": 1, title: ${h.jshtml(_('Name'))}},
             {data: "last_rev_raw", "visible": false, searchable: false},
-            {data: "last_changeset", "orderData": 3, title: "${_('Tip')}", searchable: false},
-            {data: "action", title: "${_('Action')}", sortable: false, searchable: false}
+            {data: "last_changeset", "orderData": 3, title: ${h.jshtml(_('Tip'))}, searchable: false},
+            {data: "action", title: ${h.jshtml(_('Action'))}, sortable: false, searchable: false}
         ],
         order: [[2, "asc"]],
         dom: '<"dataTables_left"f><"dataTables_right"ilp>t',
--- a/kallithea/templates/admin/my_account/my_account_watched.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/admin/my_account/my_account_watched.html	Tue Feb 28 17:19:00 2017 +0100
@@ -5,14 +5,14 @@
 </div>
 
 <script>
-  var data = ${c.data|n};
+  var data = ${h.js(c.data)};
   var myDataTable = $("#datatable_list_wrap").DataTable({
         data: data.records,
         columns: [
             {data: "raw_name", "visible": false, searchable: false},
-            {data: "name", "orderData": 1, title: "${_('Name')}"},
+            {data: "name", "orderData": 1, title: ${h.jshtml(_('Name'))}},
             {data: "last_rev_raw", "visible": false, searchable: false},
-            {data: "last_changeset", "orderData": 3, title: "${_('Tip')}", searchable: false},
+            {data: "last_changeset", "orderData": 3, title: ${h.jshtml(_('Tip'))}, searchable: false},
         ],
         order: [[2, "asc"]],
         dom: '<"dataTables_left"f><"dataTables_right"ilp>t',
--- a/kallithea/templates/admin/notifications/notifications.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/admin/notifications/notifications.html	Tue Feb 28 17:19:00 2017 +0100
@@ -34,8 +34,8 @@
     </div>
 </div>
 <script type="text/javascript">
-var url_delete = "${url('notification_delete', notification_id='__NOTIFICATION_ID__')}";
-var url_read = "${url('notification_update', notification_id='__NOTIFICATION_ID__')}";
+var url_delete = ${h.js(url('notification_delete', notification_id='__NOTIFICATION_ID__'))};
+var url_read = ${h.js(url('notification_update', notification_id='__NOTIFICATION_ID__'))};
 var run = function(){
   $('.delete-notification').click(function(e){
     var notification_id = e.currentTarget.id;
@@ -48,11 +48,11 @@
 }
 run();
 $('#mark_all_read').click(function(){
-    var url = "${h.url('notifications_mark_all_read', **request.GET.mixed())}";
+    var url = ${h.js(h.url('notifications_mark_all_read', **request.GET.mixed()))};
     asynchtml(url, $('#notification_data'), function(){run();});
 });
 
-var current_filter = "${c.current_filter}";
+var current_filter = ${h.js(c.current_filter)};
 $('#'+current_filter).addClass('active');
 </script>
 </%def>
--- a/kallithea/templates/admin/notifications/show_notification.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/admin/notifications/show_notification.html	Tue Feb 28 17:19:00 2017 +0100
@@ -43,8 +43,8 @@
     </div>
 </div>
 <script type="text/javascript">
-var url = "${url('notification_delete', notification_id='__NOTIFICATION_ID__')}";
-var main = "${url('notifications')}";
+var url = ${h.js(url('notification_delete', notification_id='__NOTIFICATION_ID__'))};
+var main = ${h.js(url('notifications'))};
    $('.delete-notification').click(function(e){
        var notification_id = e.currentTarget.id;
        deleteNotification(url,notification_id,[function(){window.location=main}]);
--- a/kallithea/templates/admin/repo_groups/repo_group_edit_perms.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/admin/repo_groups/repo_group_edit_perms.html	Tue Feb 28 17:19:00 2017 +0100
@@ -75,7 +75,7 @@
                 %endfor
 
                 <%
-                _tmpl = h.literal("""'\
+                _tmpl = """\
                     <td><input type="radio" value="group.none" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
                     <td><input type="radio" value="group.read" checked="checked" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
                     <td><input type="radio" value="group.write" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
@@ -87,7 +87,7 @@
                             <div id="perm_container_{0}"></div> \
                         </div> \
                     </td> \
-                    <td></td>'""")
+                    <td></td>"""
                 %>
                 ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'
                 <tr class="new_members last_new_member" id="add_perm_input"><td colspan="6"></td></tr>
@@ -120,7 +120,7 @@
 
 <script type="text/javascript">
     function ajaxActionRevoke(obj_id, obj_type, field_id, obj_name) {
-        url = "${h.url('edit_repo_group_perms_delete', group_name=c.repo_group.group_name)}";
+        url = ${h.jshtml(h.url('edit_repo_group_perms_delete', group_name=c.repo_group.group_name))};
         var revoke_msg = _TM['Confirm to revoke permission for {0}: {1} ?'].format(obj_type.replace('_', ' '), obj_name);
         if (confirm(revoke_msg)){
             var recursive = $('input[name=recursive]:checked').val();
@@ -133,7 +133,7 @@
             $('#add_perm_input').hide();
         }
         $('#add_perm').click(function () {
-            addPermAction(${_tmpl}, ${c.users_array|n}, ${c.user_groups_array|n});
+            addPermAction(${h.jshtml(_tmpl)}, ${h.js(c.users_array)}, ${h.js(c.user_groups_array)});
         });
     });
 </script>
--- a/kallithea/templates/admin/repo_groups/repo_groups.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/admin/repo_groups/repo_groups.html	Tue Feb 28 17:19:00 2017 +0100
@@ -31,16 +31,16 @@
     </div>
 </div>
 <script>
-  var data = ${c.data|n};
+  var data = ${h.js(c.data)};
   var myDataTable = $("#datatable_list_wrap").DataTable({
         data: data.records,
         columns: [
             {data: "raw_name", visible: false, searchable: false},
-            {data: "group_name", orderData: 0, title: "${_('Name')}"},
-            {data: "desc", title: "${_('Description')}", searchable: false},
-            {data: "repos", title: "${_('Number of Top-level Repositories')}", searchable: false},
-            {data: "owner", title: "${_('Owner')}", searchable: false},
-            {data: "action", title: "${_('Action')}", sortable: false, searchable: false}
+            {data: "group_name", orderData: 0, title: ${h.jshtml(_('Name'))}},
+            {data: "desc", title: ${h.jshtml(_('Description'))}, searchable: false},
+            {data: "repos", title: ${h.jshtml(_('Number of Top-level Repositories'))}, searchable: false},
+            {data: "owner", title: ${h.jshtml(_('Owner'))}, searchable: false},
+            {data: "action", title: ${h.jshtml(_('Action'))}, sortable: false, searchable: false}
         ],
         drawCallback: updateRowCountCallback($("#repo_group_count")),
         dom: '<"dataTables_left"f><"dataTables_right"ilp>t',
--- a/kallithea/templates/admin/repos/repo_creating.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/admin/repos/repo_creating.html	Tue Feb 28 17:19:00 2017 +0100
@@ -45,11 +45,11 @@
 <script>
 (function worker() {
   $.ajax({
-    url: '${h.url('repo_check_home', repo_name=c.repo_name, repo=c.repo, task_id=c.task_id)}',
+    url: ${h.js(h.url('repo_check_home', repo_name=c.repo_name, repo=c.repo, task_id=c.task_id))},
     success: function(data) {
       if(data.result === true){
           //redirect to created fork if our ajax loop tells us to do so.
-          window.location = "${h.url('summary_home', repo_name = c.repo)}";
+          window.location = ${h.js(h.url('summary_home', repo_name = c.repo))};
       }
     },
     complete: function(resp, status) {
--- a/kallithea/templates/admin/repos/repo_edit_permissions.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/admin/repos/repo_edit_permissions.html	Tue Feb 28 17:19:00 2017 +0100
@@ -72,7 +72,7 @@
                 %endfor
 
                 <%
-                _tmpl = h.literal("""'\
+                _tmpl = """\
                     <td><input type="radio" value="repository.none" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
                     <td><input type="radio" value="repository.read" checked="checked" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
                     <td><input type="radio" value="repository.write" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
@@ -84,7 +84,7 @@
                             <div id="perm_container_{0}"></div> \
                         </div> \
                     </td> \
-                    <td></td>'""")
+                    <td></td>"""
                 %>
                 ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'
                 <tr class="new_members last_new_member" id="add_perm_input"><td colspan="6"></td></tr>
@@ -107,7 +107,7 @@
 
 <script type="text/javascript">
     function ajaxActionRevoke(obj_id, obj_type, field_id, obj_name) {
-        url = "${h.url('edit_repo_perms_revoke',repo_name=c.repo_name)}";
+        url = ${h.js(h.url('edit_repo_perms_revoke',repo_name=c.repo_name))};
         var revoke_msg = _TM['Confirm to revoke permission for {0}: {1} ?'].format(obj_type.replace('_', ' '), obj_name);
         if (confirm(revoke_msg)){
             ajaxActionRevokePermission(url, obj_id, obj_type, field_id);
@@ -119,7 +119,7 @@
             $('#add_perm_input').hide();
         }
         $('#add_perm').click(function () {
-            addPermAction(${_tmpl}, ${c.users_array|n}, ${c.user_groups_array|n});
+            addPermAction(${h.jshtml(_tmpl)}, ${h.js(c.users_array)}, ${h.js(c.user_groups_array)});
         });
     });
 </script>
--- a/kallithea/templates/admin/repos/repo_edit_settings.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/admin/repos/repo_edit_settings.html	Tue Feb 28 17:19:00 2017 +0100
@@ -125,7 +125,7 @@
         });
 
         // autocomplete
-        var _USERS_AC_DATA = ${c.users_array|n};
+        var _USERS_AC_DATA = ${h.js(c.users_array)};
         SimpleUserAutoComplete($('#owner'), $('#owner_container'), _USERS_AC_DATA);
     });
 </script>
--- a/kallithea/templates/admin/repos/repos.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/admin/repos/repos.html	Tue Feb 28 17:19:00 2017 +0100
@@ -30,18 +30,18 @@
 
 </div>
 <script>
-  var data = ${c.data|n};
+  var data = ${h.js(c.data)};
   var myDataTable = $("#datatable_list_wrap").DataTable({
         data: data.records,
         columns: [
             {data: "raw_name", visible: false, searchable: false},
-            {data: "name", orderData: 1, title: "${_('Name')}"},
-            {data: "desc", title: "${_('Description')}", searchable: false},
+            {data: "name", orderData: 1, title: ${h.jshtml(_('Name'))}},
+            {data: "desc", title: ${h.jshtml(_('Description'))}, searchable: false},
             {data: "last_rev_raw", visible: false, searchable: false},
-            {data: "last_changeset", orderData: 4, title: "${_('Tip')}", searchable: false},
-            {data: "owner", title: "${_('Owner')}", searchable: false},
-            {data: "state", title: "${_('State')}", searchable: false},
-            {data: "action", title: "${_('Action')}", sortable: false, searchable: false}
+            {data: "last_changeset", orderData: 4, title: ${h.jshtml(_('Tip'))}, searchable: false},
+            {data: "owner", title: ${h.jshtml(_('Owner'))}, searchable: false},
+            {data: "state", title: ${h.jshtml(_('State'))}, searchable: false},
+            {data: "action", title: ${h.jshtml(_('Action'))}, sortable: false, searchable: false}
         ],
         drawCallback: updateRowCountCallback($("#repo_count")),
         dom: '<"dataTables_left"f><"dataTables_right"ilp>t',
--- a/kallithea/templates/admin/settings/settings_hooks.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/admin/settings/settings_hooks.html	Tue Feb 28 17:19:00 2017 +0100
@@ -57,12 +57,12 @@
 
 <script type="text/javascript">
 function delete_hook(hook_id, field_id) {
-    var sUrl = "${h.url('admin_settings_hooks_delete')}";
+    var sUrl = ${h.js(h.url('admin_settings_hooks_delete'))};
     var success = function (o) {
             $('#' + field_id).remove();
         };
     var failure = function (o) {
-            alert("${_('Failed to remove hook')}");
+            alert(${h.js(_('Failed to remove hook'))});
         };
     var postData = {'hook_id': hook_id};
     ajaxPOST(sUrl, postData, success, failure);
--- a/kallithea/templates/admin/settings/settings_system.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/admin/settings/settings_system.html	Tue Feb 28 17:19:00 2017 +0100
@@ -40,6 +40,6 @@
     $('#check_for_update').click(function(e){
         var $update_notice = $('#update_notice');
         $update_notice.show();
-        asynchtml("${h.url('admin_settings_system_update')}", $update_notice);
+        asynchtml(${h.js(h.url('admin_settings_system_update'))}, $update_notice);
     });
 </script>
--- a/kallithea/templates/admin/user_groups/user_group_edit_perms.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/admin/user_groups/user_group_edit_perms.html	Tue Feb 28 17:19:00 2017 +0100
@@ -75,7 +75,7 @@
                 %endfor
 
                 <%
-                _tmpl = h.literal("""'\
+                _tmpl = """\
                     <td><input type="radio" value="usergroup.none" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
                     <td><input type="radio" value="usergroup.read" checked="checked" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
                     <td><input type="radio" value="usergroup.write" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
@@ -87,7 +87,7 @@
                             <div id="perm_container_{0}"></div> \
                         </div> \
                     </td> \
-                    <td></td>'""")
+                    <td></td>"""
                 %>
                 ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'
                 <tr class="new_members last_new_member" id="add_perm_input"><td colspan="6"></td></tr>
@@ -110,7 +110,7 @@
 
 <script type="text/javascript">
     function ajaxActionRevoke(obj_id, obj_type, field_id, obj_name) {
-        url = "${h.url('edit_user_group_perms_delete', id=c.user_group.users_group_id)}";
+        url = ${h.js(h.url('edit_user_group_perms_delete', id=c.user_group.users_group_id))};
         var revoke_msg = _TM['Confirm to revoke permission for {0}: {1} ?'].format(obj_type.replace('_', ' '), obj_name);
         if (confirm(revoke_msg)){
             ajaxActionRevokePermission(url, obj_id, obj_type, field_id);
@@ -122,7 +122,7 @@
             $('#add_perm_input').hide();
         }
         $('#add_perm').click(function () {
-            addPermAction(${_tmpl}, ${c.users_array|n}, ${c.user_groups_array|n});
+            addPermAction(${h.jshtml(_tmpl)}, ${h.js(c.users_array)}, ${h.js(c.user_groups_array)});
         });
     });
 </script>
--- a/kallithea/templates/admin/user_groups/user_groups.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/admin/user_groups/user_groups.html	Tue Feb 28 17:19:00 2017 +0100
@@ -30,17 +30,17 @@
     </div>
 </div>
 <script>
-    var data = ${c.data|n};
+    var data = ${h.js(c.data)};
     var $dataTable = $("#datatable_list_wrap").DataTable({
         data: data.records,
         columns: [
             {data: "raw_name", visible: false, searchable: false},
-            {data: "group_name", title: "${_('Name')}", orderData: 0},
-            {data: "desc", title: "${_('Description')}", searchable: false},
-            {data: "members", title: "${_('Members')}", searchable: false},
-            {data: "active", title: "${_('Active')}", searchable: false, 'sType': 'str'},
-            {data: "owner", title: "${_('Owner')}", searchable: false},
-            {data: "action", title: "${_('Action')}", searchable: false, sortable: false}
+            {data: "group_name", title: ${h.jshtml(_('Name'))}, orderData: 0},
+            {data: "desc", title: ${h.jshtml(_('Description'))}, searchable: false},
+            {data: "members", title: ${h.jshtml(_('Members'))}, searchable: false},
+            {data: "active", title: ${h.jshtml(_('Active'))}, searchable: false, 'sType': 'str'},
+            {data: "owner", title: ${h.jshtml(_('Owner'))}, searchable: false},
+            {data: "action", title: ${h.jshtml(_('Action'))}, searchable: false, sortable: false}
         ],
         order: [[1, "asc"]],
         dom: '<"dataTables_left"f><"dataTables_right"ilp>t',
--- a/kallithea/templates/admin/users/users.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/admin/users/users.html	Tue Feb 28 17:19:00 2017 +0100
@@ -29,20 +29,20 @@
 </div>
 
 <script>
-    var data = ${c.data|n};
+    var data = ${h.js(c.data)};
     var $dataTable = $("#datatable_list_wrap").DataTable({
         data: data.records,
         columns: [
             {data: "gravatar", sortable: false, searchable: false},
-            {data: "username", title: "${_('Username')}"},
-            {data: "firstname", title: "${_('First Name')}"},
-            {data: "lastname", title: "${_('Last Name')}"},
+            {data: "username", title: ${h.jshtml(_('Username'))}},
+            {data: "firstname", title: ${h.jshtml(_('First Name'))}},
+            {data: "lastname", title: ${h.jshtml(_('Last Name'))}},
             {data: "last_login_raw", visible: false, searchable: false},
-            {data: "last_login", title: "${_('Last Login')}", orderData: 4, searchable: false},
-            {data: "active", title: "${_('Active')}", searchable: false, 'sType': 'str'},
-            {data: "admin", title: "${_('Admin')}", searchable: false, 'sType': 'str'},
-            {data: "extern_type", title: "${_('Auth Type')}", searchable: false},
-            {data: "action", title: "${_('Action')}", searchable: false, sortable: false}
+            {data: "last_login", title: ${h.jshtml(_('Last Login'))}, orderData: 4, searchable: false},
+            {data: "active", title: ${h.jshtml(_('Active'))}, searchable: false, 'sType': 'str'},
+            {data: "admin", title: ${h.jshtml(_('Admin'))}, searchable: false, 'sType': 'str'},
+            {data: "extern_type", title: ${h.jshtml(_('Auth Type'))}, searchable: false},
+            {data: "action", title: ${h.jshtml(_('Action'))}, searchable: false, sortable: false}
         ],
         order: [[1, "asc"]],
         drawCallback: updateRowCountCallback($("#user_count")),
--- a/kallithea/templates/base/base.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/base/base.html	Tue Feb 28 17:19:00 2017 +0100
@@ -185,7 +185,7 @@
       var bcache = {};
 
       $("#branch_switcher").select2({
-          placeholder: '<span class="navbar-text"> <i class="icon-exchange"></i> ${_('Switch To')} <span class="caret"></span></span>',
+          placeholder: '<span class="navbar-text"> <i class="icon-exchange"></i> ' + ${h.jshtml(_('Switch To'))} + ' <span class="caret"></span></span>',
           dropdownAutoWidth: true,
           sortResults: prefixFirstSort,
           formatResult: function(obj) {
@@ -195,7 +195,7 @@
               return obj.text;
           },
           formatNoMatches: function(term) {
-              return "${_('No matches found')}";
+              return ${h.jshtml(_('No matches found'))};
           },
           escapeMarkup: function(m) {
               // don't escape our custom placeholder
@@ -240,7 +240,7 @@
               } else {
                   $.ajax({
                       url: pyroutes.url('repo_refs_data', {
-                          'repo_name': '${c.repo_name}'
+                          'repo_name': ${h.js(c.repo_name)}
                       }),
                       data: {},
                       dataType: 'json',
@@ -271,7 +271,7 @@
               $("#branch_filter").val(e.choice.text).change();
           } else {
               window.location = pyroutes.url('changelog_home', {
-                  'repo_name': '${c.repo_name}',
+                  'repo_name': ${h.js(c.repo_name)},
                   'branch': e.choice.text
               });
           }
@@ -415,7 +415,7 @@
 
     <script type="text/javascript">
         $(document).ready(function(){
-            var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
+            var visual_show_public_icon = ${h.js(c.visual.show_public_icon)};
             var cache = {}
             /*format the look of items in the list*/
             var format = function(state){
@@ -449,13 +449,13 @@
             }
 
             $("#repo_switcher").select2({
-                placeholder: '<span class="navbar-text"><i class="icon-database"></i> ${_('Repositories')} <span class="caret"></span></span>',
+                placeholder: '<span class="navbar-text"><i class="icon-database"></i> ' + ${h.jshtml(_('Repositories'))} + ' <span class="caret"></span></span>',
                 dropdownAutoWidth: true,
                 sortResults: prefixFirstSort,
                 formatResult: format,
                 formatSelection: format,
                 formatNoMatches: function(term){
-                    return "${_('No matches found')}";
+                    return ${h.jshtml(_('No matches found'))};
                 },
                 containerCssClass: "repo-switcher",
                 dropdownCssClass: "repo-switcher-dropdown",
@@ -489,7 +489,7 @@
                     query.callback(data);
                   }else{
                       $.ajax({
-                        url: "${h.url('repo_switcher_data')}",
+                        url: ${h.js(h.url('repo_switcher_data'))},
                         data: {},
                         dataType: 'json',
                         type: 'GET',
--- a/kallithea/templates/base/root.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/base/root.html	Tue Feb 28 17:19:00 2017 +0100
@@ -20,43 +20,43 @@
         <script type="text/javascript">
             ## JS translations map
             var TRANSLATION_MAP = {
-                'Add Another Comment':'${_("Add Another Comment")}',
-                'Stop following this repository':"${_('Stop following this repository')}",
-                'Start following this repository':"${_('Start following this repository')}",
-                'Group':"${_('Group')}",
-                'members':"${_('members')}",
-                'Loading ...':"${_('Loading ...')}",
-                'loading ...':"${_('loading ...')}",
-                'Search truncated': "${_('Search truncated')}",
-                'No matching files': "${_('No matching files')}",
-                'Open New Pull Request from {0}': "${_('Open New Pull Request from {0}')}",
-                'Open New Pull Request for {0} &rarr; {1}': "${h.literal(_('Open New Pull Request for {0} &rarr; {1}'))}",
-                'Show Selected Changesets {0} &rarr; {1}': "${h.literal(_('Show Selected Changesets {0} &rarr; {1}'))}",
-                'Selection Link': "${_('Selection Link')}",
-                'Collapse Diff': "${_('Collapse Diff')}",
-                'Expand Diff': "${_('Expand Diff')}",
-                'Failed to revoke permission': "${_('Failed to revoke permission')}",
-                'Confirm to revoke permission for {0}: {1} ?': "${_('Confirm to revoke permission for {0}: {1} ?')}",
-                'Enabled': "${_('Enabled')}",
-                'Disabled': "${_('Disabled')}",
-                'Select changeset': "${_('Select changeset')}",
-                'Specify changeset': "${_('Specify changeset')}",
-                'MSG_SORTASC': "${_('Click to sort ascending')}",
-                'MSG_SORTDESC': "${_('Click to sort descending')}",
-                'MSG_EMPTY': "${_('No records found.')}",
-                'MSG_ERROR': "${_('Data error.')}",
-                'MSG_LOADING': "${_('Loading...')}"
+                'Add Another Comment':${h.jshtml(_("Add Another Comment"))},
+                'Stop following this repository':${h.jshtml(_('Stop following this repository'))},
+                'Start following this repository':${h.jshtml(_('Start following this repository'))},
+                'Group':${h.jshtml(_('Group'))},
+                'members':${h.jshtml(_('members'))},
+                'Loading ...':${h.jshtml(_('Loading ...'))},
+                'loading ...':${h.jshtml(_('loading ...'))},
+                'Search truncated': ${h.jshtml(_('Search truncated'))},
+                'No matching files': ${h.jshtml(_('No matching files'))},
+                'Open New Pull Request from {0}': ${h.jshtml(_('Open New Pull Request from {0}'))},
+                'Open New Pull Request for {0} &rarr; {1}': ${h.js(_('Open New Pull Request for {0} &rarr; {1}'))},
+                'Show Selected Changesets {0} &rarr; {1}': ${h.js(_('Show Selected Changesets {0} &rarr; {1}'))},
+                'Selection Link': ${h.jshtml(_('Selection Link'))},
+                'Collapse Diff': ${h.jshtml(_('Collapse Diff'))},
+                'Expand Diff': ${h.jshtml(_('Expand Diff'))},
+                'Failed to revoke permission': ${h.jshtml(_('Failed to revoke permission'))},
+                'Confirm to revoke permission for {0}: {1} ?': ${h.jshtml(_('Confirm to revoke permission for {0}: {1} ?'))},
+                'Enabled': ${h.jshtml(_('Enabled'))},
+                'Disabled': ${h.jshtml(_('Disabled'))},
+                'Select changeset': ${h.jshtml(_('Select changeset'))},
+                'Specify changeset': ${h.jshtml(_('Specify changeset'))},
+                'MSG_SORTASC': ${h.jshtml(_('Click to sort ascending'))},
+                'MSG_SORTDESC': ${h.jshtml(_('Click to sort descending'))},
+                'MSG_EMPTY': ${h.jshtml(_('No records found.'))},
+                'MSG_ERROR': ${h.jshtml(_('Data error.'))},
+                'MSG_LOADING': ${h.jshtml(_('Loading...'))}
             };
             var _TM = TRANSLATION_MAP;
 
-            var TOGGLE_FOLLOW_URL  = "${h.url('toggle_following')}";
+            var TOGGLE_FOLLOW_URL  = ${h.js(h.url('toggle_following'))};
 
             var REPO_NAME = "";
             %if hasattr(c, 'repo_name'):
-                var REPO_NAME = "${c.repo_name}";
+                var REPO_NAME = ${h.js(c.repo_name)};
             %endif
 
-            var _authentication_token = "${h.authentication_token()}";
+            var _authentication_token = ${h.js(h.authentication_token())};
         </script>
         <script type="text/javascript" src="${h.url('/js/yui.2.9.js', ver=c.kallithea_version)}"></script>
         <script type="text/javascript" src="${h.url('/js/jquery.min.js', ver=c.kallithea_version)}"></script>
@@ -82,22 +82,22 @@
               tooltip_activate();
               show_more_event();
               // routes registration
-              pyroutes.register('home', "${h.url('home')}", []);
-              pyroutes.register('new_gist', "${h.url('new_gist')}", []);
-              pyroutes.register('gists', "${h.url('gists')}", []);
-              pyroutes.register('new_repo', "${h.url('new_repo')}", []);
+              pyroutes.register('home', ${h.js(h.url('home'))}, []);
+              pyroutes.register('new_gist', ${h.js(h.url('new_gist'))}, []);
+              pyroutes.register('gists', ${h.js(h.url('gists'))}, []);
+              pyroutes.register('new_repo', ${h.js(h.url('new_repo'))}, []);
 
-              pyroutes.register('summary_home', "${h.url('summary_home', repo_name='%(repo_name)s')}", ['repo_name']);
-              pyroutes.register('changelog_home', "${h.url('changelog_home', repo_name='%(repo_name)s')}", ['repo_name']);
-              pyroutes.register('files_home', "${h.url('files_home', repo_name='%(repo_name)s',revision='%(revision)s',f_path='%(f_path)s')}", ['repo_name', 'revision', 'f_path']);
-              pyroutes.register('edit_repo', "${h.url('edit_repo', repo_name='%(repo_name)s')}", ['repo_name']);
-              pyroutes.register('edit_repo_perms', "${h.url('edit_repo_perms', repo_name='%(repo_name)s')}", ['repo_name']);
-              pyroutes.register('pullrequest_home', "${h.url('pullrequest_home', repo_name='%(repo_name)s')}", ['repo_name']);
+              pyroutes.register('summary_home', ${h.js(h.url('summary_home', repo_name='%(repo_name)s'))}, ['repo_name']);
+              pyroutes.register('changelog_home', ${h.js(h.url('changelog_home', repo_name='%(repo_name)s'))}, ['repo_name']);
+              pyroutes.register('files_home', ${h.js(h.url('files_home', repo_name='%(repo_name)s',revision='%(revision)s',f_path='%(f_path)s'))}, ['repo_name', 'revision', 'f_path']);
+              pyroutes.register('edit_repo', ${h.js(h.url('edit_repo', repo_name='%(repo_name)s'))}, ['repo_name']);
+              pyroutes.register('edit_repo_perms', ${h.js(h.url('edit_repo_perms', repo_name='%(repo_name)s'))}, ['repo_name']);
+              pyroutes.register('pullrequest_home', ${h.js(h.url('pullrequest_home', repo_name='%(repo_name)s'))}, ['repo_name']);
 
-              pyroutes.register('toggle_following', "${h.url('toggle_following')}");
-              pyroutes.register('changeset_info', "${h.url('changeset_info', repo_name='%(repo_name)s', revision='%(revision)s')}", ['repo_name', 'revision']);
-              pyroutes.register('repo_size', "${h.url('repo_size', repo_name='%(repo_name)s')}", ['repo_name']);
-              pyroutes.register('repo_refs_data', "${h.url('repo_refs_data', repo_name='%(repo_name)s')}", ['repo_name']);
+              pyroutes.register('toggle_following', ${h.js(h.url('toggle_following'))});
+              pyroutes.register('changeset_info', ${h.js(h.url('changeset_info', repo_name='%(repo_name)s', revision='%(revision)s'))}, ['repo_name', 'revision']);
+              pyroutes.register('repo_size', ${h.js(h.url('repo_size', repo_name='%(repo_name)s'))}, ['repo_name']);
+              pyroutes.register('repo_refs_data', ${h.js(h.url('repo_refs_data', repo_name='%(repo_name)s'))}, ['repo_name']);
              });
         </script>
 
--- a/kallithea/templates/changelog/changelog.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/changelog/changelog.html	Tue Feb 28 17:19:00 2017 +0100
@@ -189,7 +189,7 @@
             $(document).ready(function(){
                 var $checkboxes = $('.changeset_range');
 
-                pyroutes.register('changeset_home', "${h.url('changeset_home', repo_name='%(repo_name)s', revision='%(revision)s')}", ['repo_name', 'revision']);
+                pyroutes.register('changeset_home', ${h.js(h.url('changeset_home', repo_name='%(repo_name)s', revision='%(revision)s'))}, ['repo_name', 'revision']);
 
                 var checkbox_checker = function(e) {
                     var $checked_checkboxes = $checkboxes.filter(':checked');
@@ -206,19 +206,19 @@
                         if ($checked_checkboxes.length > 1 || singlerange) {
                             var rev_start = $checked_checkboxes.last().prop('name');
                             $('#rev_range_container').prop('href',
-                                pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}',
+                                pyroutes.url('changeset_home', {'repo_name': ${h.js(c.repo_name)},
                                                                 'revision': rev_start + '...' + rev_end}));
                             $('#rev_range_container').html(
                                  _TM['Show Selected Changesets {0} &rarr; {1}'].format(rev_start.substr(0, 12), rev_end.substr(0, 12)));
                             $('#rev_range_container').show();
                             $('#open_new_pr').prop('href', pyroutes.url('pullrequest_home',
-                                                                        {'repo_name': '${c.repo_name}',
+                                                                        {'repo_name': ${h.js(c.repo_name)},
                                                                          'rev_start': rev_start,
                                                                          'rev_end': rev_end}));
                             $('#open_new_pr').html(_TM['Open New Pull Request for {0} &rarr; {1}'].format(rev_start.substr(0, 12), rev_end.substr(0, 12)));
                         } else {
                             $('#open_new_pr').prop('href', pyroutes.url('pullrequest_home',
-                                                                        {'repo_name': '${c.repo_name}',
+                                                                        {'repo_name': ${h.js(c.repo_name)},
                                                                          'rev_end': rev_end}));
                             $('#open_new_pr').html(_TM['Open New Pull Request from {0}'].format(rev_end.substr(0, 12)));
                         }
@@ -252,14 +252,14 @@
                         $('#rev_range_clear').hide();
                         %if c.revision:
                             $('#open_new_pr').prop('href', pyroutes.url('pullrequest_home',
-                                                                        {'repo_name': '${c.repo_name}',
-                                                                         'rev_end':'${c.first_revision.raw_id}'}));
-                            $('#open_new_pr').html(_TM['Open New Pull Request from {0}'].format('${c.revision}'));
+                                                                        {'repo_name': ${h.js(c.repo_name)},
+                                                                         'rev_end':${h.js(c.first_revision.raw_id)}}));
+                            $('#open_new_pr').html(_TM['Open New Pull Request from {0}'].format(${h.jshtml(c.revision)}));
                         %else:
                             $('#open_new_pr').prop('href', pyroutes.url('pullrequest_home',
-                                                                        {'repo_name': '${c.repo_name}',
-                                                                        'branch':'${c.first_revision.branch}'}));
-                            $('#open_new_pr').html(_TM['Open New Pull Request from {0}'].format('${c.first_revision.branch}'));
+                                                                        {'repo_name': ${h.js(c.repo_name)},
+                                                                        'branch':${h.js(c.first_revision.branch)}}));
+                            $('#open_new_pr').html(_TM['Open New Pull Request from {0}'].format(${h.jshtml(c.first_revision.branch)}));
                         %endif
                         $('#compare_fork').show();
                         $checkboxes.closest('tr').removeClass('out-of-range');
@@ -311,15 +311,15 @@
                 $("#branch_filter").change(function(e){
                     var selected_branch = e.currentTarget.options[e.currentTarget.selectedIndex].value;
                     if(selected_branch != ''){
-                        window.location = pyroutes.url('changelog_home', {'repo_name': '${c.repo_name}',
+                        window.location = pyroutes.url('changelog_home', {'repo_name': ${h.js(c.repo_name)},
                                                                           'branch': selected_branch});
                     }else{
-                        window.location = pyroutes.url('changelog_home', {'repo_name': '${c.repo_name}'});
+                        window.location = pyroutes.url('changelog_home', {'repo_name': ${h.js(c.repo_name)}});
                     }
                     $("#changelog").hide();
                 });
 
-                var jsdata = ${c.jsdata|n};
+                var jsdata = ${h.js(c.jsdata)};
                 var r = new BranchRenderer('graph_canvas', 'graph_content', 'chg_');
                 r.render(jsdata);
             });
--- a/kallithea/templates/changeset/changeset.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/changeset/changeset.html	Tue Feb 28 17:19:00 2017 +0100
@@ -23,10 +23,10 @@
     ${self.breadcrumbs()}
   </div>
   <script>
-    var _USERS_AC_DATA = ${c.users_array|n};
-    var _GROUPS_AC_DATA = ${c.user_groups_array|n};
-    AJAX_COMMENT_URL = "${url('changeset_comment',repo_name=c.repo_name,revision=c.changeset.raw_id)}";
-    AJAX_COMMENT_DELETE_URL = "${url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
+    var _USERS_AC_DATA = ${h.js(c.users_array)};
+    var _GROUPS_AC_DATA = ${h.js(c.user_groups_array)};
+    AJAX_COMMENT_URL = ${h.js(url('changeset_comment',repo_name=c.repo_name,revision=c.changeset.raw_id))};
+    AJAX_COMMENT_DELETE_URL = ${h.js(url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__'))};
   </script>
   <div class="panel-body">
     <div>
@@ -217,7 +217,7 @@
           move_comments($(".comments .comments-list-chunk"));
 
           pyroutes.register('changeset_home',
-                            "${h.url('changeset_home', repo_name='%(repo_name)s', revision='%(revision)s')}",
+                            ${h.js(h.url('changeset_home', repo_name='%(repo_name)s', revision='%(revision)s'))},
                             ['repo_name', 'revision']);
 
           //next links
@@ -226,15 +226,15 @@
               //>1 links show them to user to choose
               if(!$('#child_link').hasClass('disabled')){
                   $.ajax({
-                    url: '${h.url('changeset_children',repo_name=c.repo_name, revision=c.changeset.raw_id)}',
+                    url: ${h.js(h.url('changeset_children',repo_name=c.repo_name, revision=c.changeset.raw_id))},
                     success: function(data) {
                       if(data.results.length === 0){
                           $('#child_link').addClass('disabled');
-                          $('#child_link').html('${_('No revisions')}');
+                          $('#child_link').html(${h.jshtml(_('No revisions'))});
                       }
                       if(data.results.length === 1){
                           var commit = data.results[0];
-                          window.location = pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': commit.raw_id});
+                          window.location = pyroutes.url('changeset_home', {'repo_name': ${h.js(c.repo_name)},'revision': commit.raw_id});
                       }
                       else if(data.results.length === 2){
                           $('#child_link').addClass('disabled');
@@ -243,12 +243,12 @@
                           _html +='<a title="__title__" href="__url__">__rev__</a> <i style="color:#036185" class="icon-right-open"></i>'
                                   .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
                                   .replace('__title__', data.results[0].message)
-                                  .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[0].raw_id}));
+                                  .replace('__url__', pyroutes.url('changeset_home', {'repo_name': ${h.js(c.repo_name)},'revision': data.results[0].raw_id}));
                           _html +='<br/>'
                           _html +='<a title="__title__" href="__url__">__rev__</a> <i style="color:#036185" class="icon-right-open"></i>'
                                   .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
                                   .replace('__title__', data.results[1].message)
-                                  .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[1].raw_id}));
+                                  .replace('__url__', pyroutes.url('changeset_home', {'repo_name': ${h.js(c.repo_name)},'revision': data.results[1].raw_id}));
                           $('#child_link').html(_html);
                       }
                     }
@@ -263,15 +263,15 @@
               //>1 links show them to user to choose
               if(!$('#parent_link').hasClass('disabled')){
                   $.ajax({
-                    url: '${h.url('changeset_parents',repo_name=c.repo_name, revision=c.changeset.raw_id)}',
+                    url: ${h.js(h.url('changeset_parents',repo_name=c.repo_name, revision=c.changeset.raw_id))},
                     success: function(data) {
                       if(data.results.length === 0){
                           $('#parent_link').addClass('disabled');
-                          $('#parent_link').html('${_('No revisions')}');
+                          $('#parent_link').html(${h.jshtml(_('No revisions'))});
                       }
                       if(data.results.length === 1){
                           var commit = data.results[0];
-                          window.location = pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': commit.raw_id});
+                          window.location = pyroutes.url('changeset_home', {'repo_name': ${h.js(c.repo_name)},'revision': commit.raw_id});
                       }
                       else if(data.results.length === 2){
                           $('#parent_link').addClass('disabled');
@@ -280,12 +280,12 @@
                           _html +='<i style="color:#036185" class="icon-left-open"></i> <a title="__title__" href="__url__">__rev__</a>'
                                   .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
                                   .replace('__title__', data.results[0].message)
-                                  .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[0].raw_id}));
+                                  .replace('__url__', pyroutes.url('changeset_home', {'repo_name': ${h.js(c.repo_name)},'revision': data.results[0].raw_id}));
                           _html +='<br/>'
                           _html +='<i style="color:#036185" class="icon-left-open"></i> <a title="__title__" href="__url__">__rev__</a>'
                                   .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
                                   .replace('__title__', data.results[1].message)
-                                  .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[1].raw_id}));
+                                  .replace('__url__', pyroutes.url('changeset_home', {'repo_name': ${h.js(c.repo_name)},'revision': data.results[1].raw_id}));
                           $('#parent_link').html(_html);
                       }
                     }
--- a/kallithea/templates/compare/compare_cs.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/compare/compare_cs.html	Tue Feb 28 17:19:00 2017 +0100
@@ -121,7 +121,7 @@
 </div>
 
 %if c.as_form:
-<div id="jsdata" style="display:none">${c.jsdata|n}</div>
+<div id="jsdata" style="display:none">${h.js(c.jsdata)}</div>
 %else:
 <script type="text/javascript" src="${h.url('/js/graph.js', ver=c.kallithea_version)}"></script>
 %endif
@@ -130,7 +130,7 @@
 
     $(document).ready(function(){
 %if not c.as_form:
-        var jsdata = ${c.jsdata|n};
+        var jsdata = ${h.js(c.jsdata)};
         var r = new BranchRenderer('graph_canvas', 'graph_content_pr', 'chg_');
         r.render(jsdata);
 %endif
--- a/kallithea/templates/compare/compare_diff.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/compare/compare_diff.html	Tue Feb 28 17:19:00 2017 +0100
@@ -149,8 +149,8 @@
     });
     }
 
-    make_revision_dropdown("#compare_org",   "${'%s@%s' % (c.a_repo.repo_name, c.a_ref_name)}",   "${c.a_repo.repo_name}",  'cache');
-    make_revision_dropdown("#compare_other", "${'%s@%s' % (c.cs_repo.repo_name, c.cs_ref_name)}", "${c.cs_repo.repo_name}", 'cache2');
+    make_revision_dropdown("#compare_org",   ${h.jshtml('%s@%s' % (c.a_repo.repo_name, c.a_ref_name))},   ${h.jshtml(c.a_repo.repo_name)},  'cache');
+    make_revision_dropdown("#compare_other", ${h.jshtml('%s@%s' % (c.cs_repo.repo_name, c.cs_ref_name))}, ${h.jshtml(c.cs_repo.repo_name)}, 'cache2');
 
     var values_changed = function() {
         var values = $('#compare_org').select2('data') && $('#compare_other').select2('data');
@@ -172,7 +172,7 @@
             return;
         }
 
-        var compare_url = "${h.url('compare_url',repo_name=c.repo_name,org_ref_type='__other_ref_type__',org_ref_name='__org__',other_ref_type='__org_ref_type__',other_ref_name='__other__', other_repo=c.cs_repo.repo_name)}";
+        var compare_url = ${h.js(h.url('compare_url',repo_name=c.repo_name,org_ref_type='__other_ref_type__',org_ref_name='__org__',other_ref_type='__org_ref_type__',other_ref_name='__other__', other_repo=c.cs_repo.repo_name))};
         var u = compare_url.replace('__other_ref_type__',org.type)
                            .replace('__org__',org.text)
                            .replace('__org_ref_type__',other.type)
--- a/kallithea/templates/files/diff_2way.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/files/diff_2way.html	Tue Feb 28 17:19:00 2017 +0100
@@ -61,8 +61,8 @@
     </div>
 
 <script>
-var orig1_url = '${h.url('files_raw_home',repo_name=c.repo_name,f_path=h.safe_unicode(c.node1.path),revision=c.cs1.raw_id)}';
-var orig2_url = '${h.url('files_raw_home',repo_name=c.repo_name,f_path=h.safe_unicode(c.node2.path),revision=c.cs2.raw_id)}';
+var orig1_url = ${h.jshtml(h.url('files_raw_home',repo_name=c.repo_name,f_path=h.safe_unicode(c.node1.path),revision=c.cs1.raw_id))};
+var orig2_url = ${h.jshtml(h.url('files_raw_home',repo_name=c.repo_name,f_path=h.safe_unicode(c.node2.path),revision=c.cs2.raw_id))};
 
 $(document).ready(function () {
     $('#compare').mergely({
@@ -73,7 +73,7 @@
         viewport: true,
         cmsettings: {mode: 'text/plain', readOnly: true, lineWrapping: false, lineNumbers: true},
         lhs: function(setValue) {
-            if("${c.node1.is_binary}" == "True"){
+            if (${h.js(c.node1.is_binary)}) {
                 setValue('Binary file');
             }
             else{
@@ -82,7 +82,7 @@
 
         },
         rhs: function(setValue) {
-            if("${c.node2.is_binary}" == "True"){
+            if (${h.js(c.node2.is_binary)}) {
                 setValue('Binary file');
             }
             else{
--- a/kallithea/templates/files/files.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/files/files.html	Tue Feb 28 17:19:00 2017 +0100
@@ -40,14 +40,14 @@
 var CACHE = {};
 var CACHE_EXPIRE = 5*60*1000; //cache for 5*60s
 //used to construct links from the search list
-var url_base = '${h.url("files_home",repo_name=c.repo_name,revision='__REV__',f_path='__FPATH__')}';
+var url_base = ${h.js(h.url("files_home",repo_name=c.repo_name,revision='__REV__',f_path='__FPATH__'))};
 //send the nodelist request to this url
-var node_list_url = '${h.url("files_nodelist_home",repo_name=c.repo_name,revision='__REV__',f_path='__FPATH__')}';
+var node_list_url = ${h.js(h.url("files_nodelist_home",repo_name=c.repo_name,revision='__REV__',f_path='__FPATH__'))};
 
 ## new pyroutes URLs
-pyroutes.register('files_nodelist_home', "${h.url('files_nodelist_home', repo_name=c.repo_name,revision='%(revision)s',f_path='%(f_path)s')}", ['revision', 'f_path']);
-pyroutes.register('files_history_home', "${h.url('files_history_home', repo_name=c.repo_name,revision='%(revision)s',f_path='%(f_path)s')}", ['revision', 'f_path']);
-pyroutes.register('files_authors_home', "${h.url('files_authors_home', repo_name=c.repo_name,revision='%(revision)s',f_path='%(f_path)s')}", ['revision', 'f_path']);
+pyroutes.register('files_nodelist_home', ${h.js(h.url('files_nodelist_home', repo_name=c.repo_name,revision='%(revision)s',f_path='%(f_path)s'))}, ['revision', 'f_path']);
+pyroutes.register('files_history_home', ${h.js(h.url('files_history_home', repo_name=c.repo_name,revision='%(revision)s',f_path='%(f_path)s'))}, ['revision', 'f_path']);
+pyroutes.register('files_authors_home', ${h.js(h.url('files_authors_home', repo_name=c.repo_name,revision='%(revision)s',f_path='%(f_path)s'))}, ['revision', 'f_path']);
 
 var ypjax_links = function(){
     $('.ypjax-link').click(function(e){
@@ -60,7 +60,7 @@
         var el = e.currentTarget;
         var url = el.href;
 
-        var _base_url = '${h.url("files_home",repo_name=c.repo_name,revision='',f_path='')}';
+        var _base_url = ${h.jshtml(h.url("files_home",repo_name=c.repo_name,revision='',f_path=''))};
         _base_url = _base_url.replace('//','/');
 
         //extract rev and the f_path from url.
@@ -74,7 +74,7 @@
         var f_path = parts2.join('/');
 
         //page title - make this consistent with title mako block above
-        var title = "${_('%s Files') % c.repo_name}" + " \u00B7 " + (f_path || '/') + " \u00B7 " + "${c.site_name}";
+        var title = ${h.jshtml(_('%s Files') % c.repo_name)} + " \u00B7 " + (f_path || '/') + " \u00B7 " + ${h.jshtml(c.site_name)};
 
         var _node_list_url = node_list_url.replace('__REV__',rev).replace('__FPATH__', f_path);
         var _url_base = url_base.replace('__REV__',rev);
@@ -225,12 +225,12 @@
 
     // init the search filter
     var _State = {
-       url: "${h.url.current()}",
+       url: ${h.js(h.url.current())},
        data: {
-         node_list_url: node_list_url.replace('__REV__',"${c.changeset.raw_id}").replace('__FPATH__', "${h.safe_unicode(c.file.path)}"),
-         url_base: url_base.replace('__REV__',"${c.changeset.raw_id}"),
-         rev:"${c.changeset.raw_id}",
-         f_path: "${h.safe_unicode(c.file.path)}"
+         node_list_url: node_list_url.replace('__REV__',${h.js(c.changeset.raw_id)}).replace('__FPATH__', ${h.js(h.safe_unicode(c.file.path))}),
+         url_base: url_base.replace('__REV__',${h.js(c.changeset.raw_id)}),
+         rev:${h.js(c.changeset.raw_id)},
+         f_path: ${h.js(h.safe_unicode(c.file.path))}
        }
     }
     fileBrowserListeners(_State.url, _State.data.node_list_url, _State.data.url_base);
@@ -244,11 +244,11 @@
 
     $("#branch_selector").change(function(e){
         var selected = e.currentTarget.options[e.currentTarget.selectedIndex].value;
-        if(selected && selected != "${c.changeset.raw_id}"){
-            window.location = pyroutes.url('files_home', {'repo_name': "${h.safe_unicode(c.repo_name)}", 'revision': selected, 'f_path': "${h.safe_unicode(c.file.path)}"});
+        if(selected && selected != ${h.js(c.changeset.raw_id)}){
+            window.location = pyroutes.url('files_home', {'repo_name': ${h.js(h.safe_unicode(c.repo_name))}, 'revision': selected, 'f_path': ${h.js(h.safe_unicode(c.file.path))}});
             $("#body.browserblock").hide();
         } else {
-            $("#branch_selector").val("${c.changeset.raw_id}");
+            $("#branch_selector").val(${h.js(c.changeset.raw_id)});
         }
     });
 
--- a/kallithea/templates/files/files_add.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/files/files_add.html	Tue Feb 28 17:19:00 2017 +0100
@@ -68,8 +68,8 @@
             ${h.end_form()}
             <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.f_path)}";
-                    var myCodeMirror = initCodeMirror('editor', "${request.script_name}", reset_url);
+                    var reset_url = ${h.jshtml(h.url('files_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path))};
+                    var myCodeMirror = initCodeMirror('editor', ${h.jshtml(request.script_name)}, reset_url);
 
                     //inject new modes, based on codeMirrors modeInfo object
                     var $mimetype_select = $('#mimetype');
--- a/kallithea/templates/files/files_browser.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/files/files_browser.html	Tue Feb 28 17:19:00 2017 +0100
@@ -120,7 +120,7 @@
 <script>
     $(document).ready(function(){
         // init node filter if we pass GET param ?search=1
-        var search_GET = "${request.GET.get('search','')}";
+        var search_GET = ${h.js(request.GET.get('search',''))};
         if(search_GET == "1"){
             $("#filter_activate").click();
         }
--- a/kallithea/templates/files/files_edit.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/files/files_edit.html	Tue Feb 28 17:19:00 2017 +0100
@@ -77,8 +77,8 @@
 
 <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);
+        var reset_url = ${h.jshtml(h.url('files_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.file.path))};
+        var myCodeMirror = initCodeMirror('editor', ${h.jshtml(request.script_name)}, reset_url);
 
        //inject new modes, based on codeMirrors modeInfo object
         var $mimetype_select = $('#mimetype');
@@ -99,7 +99,7 @@
             }
         });
         // try to detect the mode based on the file we edit
-        var detected_mode = CodeMirror.findModeByExtension("${c.file.extension}");
+        var detected_mode = CodeMirror.findModeByExtension(${h.js(c.file.extension)});
         if(detected_mode){
             setCodeMirrorMode(myCodeMirror, detected_mode);
             $($mimetype_select.find('option[value="'+detected_mode.mime+'"]')[0]).prop('selected', true);
--- a/kallithea/templates/files/files_source.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/files/files_source.html	Tue Feb 28 17:19:00 2017 +0100
@@ -88,12 +88,12 @@
     $(document).ready(function(){
         // fake html5 history state
         var _State = {
-           url: "${h.url.current()}",
+           url: ${h.js(h.url.current())},
            data: {
-             node_list_url: node_list_url.replace('__REV__',"${c.changeset.raw_id}").replace('__FPATH__', "${h.safe_unicode(c.file.path)}"),
-             url_base: url_base.replace('__REV__',"${c.changeset.raw_id}"),
-             rev:"${c.changeset.raw_id}",
-             f_path: "${h.safe_unicode(c.file.path)}"
+             node_list_url: node_list_url.replace('__REV__',${h.js(c.changeset.raw_id)}).replace('__FPATH__', ${h.js(h.safe_unicode(c.file.path))}),
+             url_base: url_base.replace('__REV__',${h.js(c.changeset.raw_id)}),
+             rev:${h.js(c.changeset.raw_id)},
+             f_path: ${h.js(h.safe_unicode(c.file.path))}
            }
         }
         callbacks(_State); // defined in files.html, main callbacks. Triggered in pjax calls
--- a/kallithea/templates/index_base.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/index_base.html	Tue Feb 28 17:19:00 2017 +0100
@@ -83,22 +83,22 @@
             pageLength: 100
         });
 
-        var data = ${c.data|n},
+        var data = ${h.js(c.data)},
             $dataTable = $("#repos_list_wrap").DataTable({
                 data: data.records,
                 columns: [
                     {data: "raw_name", visible: false, searchable: false},
-                    {title: "${_('Repository')}", data: "name", orderData: [0,], render: {
+                    {title: ${h.jshtml(_('Repository'))}, data: "name", orderData: [0,], render: {
                         filter: function(data, type, row, meta) {
                             return row.just_name;
                         }
                     }},
-                    {data: "desc", title: "${_('Description')}", searchable: false},
+                    {data: "desc", title: ${h.jshtml(_('Description'))}, searchable: false},
                     {data: "last_change_iso", visible: false, searchable: false},
-                    {data: "last_change", title: "${_('Last Change')}", orderData: [3,], searchable: false},
+                    {data: "last_change", title: ${h.jshtml(_('Last Change'))}, orderData: [3,], searchable: false},
                     {data: "last_rev_raw", visible: false, searchable: false},
-                    {data: "last_changeset", title: "${_('Tip')}", orderData: [5,], searchable: false},
-                    {data: "owner", title: "${_('Owner')}", searchable: false},
+                    {data: "last_changeset", title: ${h.jshtml(_('Tip'))}, orderData: [5,], searchable: false},
+                    {data: "owner", title: ${h.jshtml(_('Owner'))}, searchable: false},
                     {data: "atom", sortable: false}
                 ],
                 order: [[1, "asc"]],
--- a/kallithea/templates/journal/journal.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/journal/journal.html	Tue Feb 28 17:19:00 2017 +0100
@@ -58,12 +58,12 @@
     $('#filter_form').submit(function(e){
         e.preventDefault();
         var val = $('#j_filter').val();
-        window.location = "${url.current(filter='__FILTER__')}".replace('__FILTER__',val);
+        window.location = ${h.js(url.current(filter='__FILTER__'))}.replace('__FILTER__',val);
     });
     fix_j_filter_width($('#j_filter').val().length);
 
     $('#refresh').click(function(e){
-        asynchtml("${h.url.current(filter=c.search_term)}", $("#journal"), function(){
+        asynchtml(${h.js(h.url.current(filter=c.search_term))}, $("#journal"), function(){
             show_more_event();
             tooltip_activate();
             });
--- a/kallithea/templates/password_reset.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/password_reset.html	Tue Feb 28 17:19:00 2017 +0100
@@ -55,7 +55,7 @@
          $(document).ready(function(){
             $('#email').focus();
             %if c.captcha_active:
-            Recaptcha.create("${c.captcha_public_key}", "recaptcha",
+            Recaptcha.create(${h.js(c.captcha_public_key)}, "recaptcha",
                 {
                   theme: "white"
                 }
--- a/kallithea/templates/pullrequests/pullrequest.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/pullrequests/pullrequest.html	Tue Feb 28 17:19:00 2017 +0100
@@ -97,7 +97,7 @@
 
 <script type="text/javascript" src="${h.url('/js/graph.js', ver=c.kallithea_version)}"></script>
 <script type="text/javascript">
-  pyroutes.register('pullrequest_repo_info', "${url('pullrequest_repo_info',repo_name='%(repo_name)s')}", ['repo_name']);
+  pyroutes.register('pullrequest_repo_info', ${h.js(url('pullrequest_repo_info',repo_name='%(repo_name)s'))}, ['repo_name']);
 
   var pendingajax = undefined;
   var otherrepoChanged = function(){
@@ -140,7 +140,7 @@
 
   var loadPreview = function(){
       //url template
-      var url = "${h.url('compare_url',
+      var url = ${h.js(h.url('compare_url',
                          repo_name='__other_repo__',
                          org_ref_type='rev',
                          org_ref_name='__other_ref_name__',
@@ -149,7 +149,7 @@
                          other_ref_name='__org_ref_name__',
                          as_form=True,
                          merge=True,
-                         )}";
+                         ))};
       var org_repo = $('#pull_request_form #org_repo').val();
       var org_ref = $('#pull_request_form #org_ref').val().split(':');
       ## TODO: make nice link like link_to_ref() do
--- a/kallithea/templates/pullrequests/pullrequest_show.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/pullrequests/pullrequest_show.html	Tue Feb 28 17:19:00 2017 +0100
@@ -331,14 +331,14 @@
         </div>
     </div>
     <script>
-    var _USERS_AC_DATA = ${c.users_array|n};
-    var _GROUPS_AC_DATA = ${c.user_groups_array|n};
+    var _USERS_AC_DATA = ${h.js(c.users_array)};
+    var _GROUPS_AC_DATA = ${h.js(c.user_groups_array)};
     // TODO: switch this to pyroutes
-    AJAX_COMMENT_URL = "${url('pullrequest_comment',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}";
-    AJAX_COMMENT_DELETE_URL = "${url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
+    AJAX_COMMENT_URL = ${h.js(url('pullrequest_comment',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id))};
+    AJAX_COMMENT_DELETE_URL = ${h.js(url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__'))};
 
-    pyroutes.register('pullrequest_comment', "${url('pullrequest_comment',repo_name='%(repo_name)s',pull_request_id='%(pull_request_id)s')}", ['repo_name', 'pull_request_id']);
-    pyroutes.register('pullrequest_comment_delete', "${url('pullrequest_comment_delete',repo_name='%(repo_name)s',comment_id='%(comment_id)s')}", ['repo_name', 'comment_id']);
+    pyroutes.register('pullrequest_comment', ${h.js(url('pullrequest_comment',repo_name='%(repo_name)s',pull_request_id='%(pull_request_id)s'))}, ['repo_name', 'pull_request_id']);
+    pyroutes.register('pullrequest_comment_delete', ${h.js(url('pullrequest_comment_delete',repo_name='%(repo_name)s',comment_id='%(comment_id)s'))}, ['repo_name', 'comment_id']);
 
     </script>
 
@@ -372,7 +372,7 @@
               show_comment_form($(this));
           });
 
-          var avail_jsdata = ${c.avail_jsdata|n};
+          var avail_jsdata = ${h.js(c.avail_jsdata)};
           var avail_r = new BranchRenderer('avail_graph_canvas', 'updaterevs-table', 'chg_available_');
           avail_r.render(avail_jsdata);
 
--- a/kallithea/templates/register.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/register.html	Tue Feb 28 17:19:00 2017 +0100
@@ -92,7 +92,7 @@
             $('#username').focus();
 
             %if c.captcha_active:
-            Recaptcha.create("${c.captcha_public_key}", "recaptcha",
+            Recaptcha.create(${h.js(c.captcha_public_key)}, "recaptcha",
                 {
                   theme: "white"
                 }
--- a/kallithea/templates/summary/statistics.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/summary/statistics.html	Tue Feb 28 17:19:00 2017 +0100
@@ -53,7 +53,7 @@
 </div>
 
 <script type="text/javascript">
-var data = ${c.trending_languages|n};
+var data = ${h.js(c.trending_languages)};
 var total = 0;
 var no_data = true;
 var tbl = document.createElement('table');
@@ -85,7 +85,7 @@
     var td2 = document.createElement('td');
     td2.setAttribute('style','padding-right:14px !important');
     var trending_language = document.createElement('div');
-    var nr_files = obj.count+" ${_('files')}";
+    var nr_files = obj.count + ' ' + ${h.jshtml(_('files'))};
 
     trending_language.title = k+" "+nr_files;
 
@@ -109,7 +109,7 @@
         lnk = document.createElement('a');
 
         lnk.href='#';
-        lnk.innerHTML = "${_('Show more')}";
+        lnk.innerHTML = ${h.jshtml(_('Show more'))};
         lnk.id='code_stats_show_more';
         td.appendChild(lnk);
 
@@ -389,15 +389,15 @@
                 var changed = cur_data.changed;
                 var removed = cur_data.removed;
 
-                var nr_commits_suffix = " ${_('commits')} ";
-                var added_suffix = " ${_('files added')} ";
-                var changed_suffix = " ${_('files changed')} ";
-                var removed_suffix = " ${_('files removed')} ";
+                var nr_commits_suffix = ' ' + ${h.jshtml(_('commits'))} + ' ';
+                var added_suffix = ' ' + ${h.jshtml(_('files added'))} + ' ';
+                var changed_suffix = ' ' + ${h.jshtml(_('files changed'))} + ' ';
+                var removed_suffix = ' ' + ${h.jshtml(_('files removed'))} + ' ';
 
-                if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
-                if(added==1){added_suffix=" ${_('file added')} ";}
-                if(changed==1){changed_suffix=" ${_('file changed')} ";}
-                if(removed==1){removed_suffix=" ${_('file removed')} ";}
+                if(nr_commits == 1){ nr_commits_suffix = ' ' + ${h.jshtml(_('commit'))} + ' '; }
+                if(added == 1) { added_suffix=' ' + ${h.jshtml(_('file added'))} + ' '; }
+                if(changed == 1) { changed_suffix=' ' + ${h.jshtml(_('file changed'))} + ' '; }
+                if(removed == 1) { removed_suffix=' ' + ${h.jshtml(_('file removed'))} + ' '; }
 
                 showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
                          +'<br/>'+
@@ -444,7 +444,7 @@
     // user choices on overview
     YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]);
 }
-    SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n});
+    SummaryPlot(${h.jshtml(c.ts_min)}, ${h.jshtml(c.ts_max)}, ${h.js(c.commit_data)}, ${h.js(c.overview_data)});
 </script>
 
 </%def>
--- a/kallithea/templates/summary/summary.html	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/templates/summary/summary.html	Tue Feb 28 17:19:00 2017 +0100
@@ -43,7 +43,7 @@
   redirect_hash_branch = function(){
     var branch = window.location.hash.replace(/^#(.*)/, '$1');
     if (branch){
-      window.location = "${h.url('changelog_home',repo_name=c.repo_name,branch='__BRANCH__')}"
+      window.location = ${h.js(h.url('changelog_home',repo_name=c.repo_name,branch='__BRANCH__'))}
         .replace('__BRANCH__',branch);
     }
   }
@@ -252,7 +252,7 @@
             query.callback(data);
           }else{
               $.ajax({
-                url: pyroutes.url('repo_refs_data', {'repo_name': '${c.repo_name}'}),
+                url: pyroutes.url('repo_refs_data', {'repo_name': ${h.js(c.repo_name)}}),
                 data: {},
                 dataType: 'json',
                 type: 'GET',
@@ -271,7 +271,7 @@
        for(k in tmpl_links){
            var s = $('#'+k+'_link');
            if(s){
-             var title_tmpl = "${_('Download %s as %s') % ('__CS_NAME__','__CS_EXT__')}";
+             var title_tmpl = ${h.jshtml(_('Download %s as %s') % ('__CS_NAME__','__CS_EXT__'))};
              title_tmpl= title_tmpl.replace('__CS_NAME__',new_cs.text);
              title_tmpl = title_tmpl.replace('__CS_EXT__',k);
              title_tmpl = '<i class="icon-file-zip"></i> '+ title_tmpl;
@@ -287,7 +287,7 @@
 
     var tmpl_links = {};
     %for cnt,archive in enumerate(c.db_repo_scm_instance._get_archives()):
-      tmpl_links["${archive['type']}"] = '${h.link_to('__NAME__', h.url('files_archive_home',repo_name=c.db_repo.repo_name, fname='__CS__'+archive['extension'],subrepos='__SUB__'),class_='btn btn-default btn-sm')}';
+      tmpl_links[${h.jshtml(archive['type'])}] = ${h.js(h.link_to('__NAME__', h.url('files_archive_home',repo_name=c.db_repo.repo_name, fname='__CS__'+archive['extension'],subrepos='__SUB__'),class_='btn btn-default btn-sm'))};
     %endfor
 });
 </script>
@@ -295,7 +295,7 @@
 %if c.show_stats:
 <script type="text/javascript">
 $(document).ready(function(){
-    var data = ${c.trending_languages|n};
+    var data = ${h.js(c.trending_languages)};
     var total = 0;
     var no_data = true;
     var tbl = document.createElement('table');
@@ -327,7 +327,7 @@
         var td2 = document.createElement('td');
         td2.setAttribute('style','padding-right:14px !important');
         var trending_language = document.createElement('div');
-        var nr_files = obj.count+" ${_('files')}";
+        var nr_files = obj.count + ' ' + ${h.jshtml(_('files'))};
 
         trending_language.title = k+" "+nr_files;
 
@@ -353,7 +353,7 @@
             lnk = document.createElement('a');
 
             lnk.href='#';
-            lnk.innerHTML = "${_('Show more')}";
+            lnk.innerHTML = ${h.jshtml(_('Show more'))};
             lnk.id='code_stats_show_more';
             td.appendChild(lnk);
 
@@ -364,7 +364,7 @@
 
     }
     if (data.length == 0) {
-        tbl.innerHTML = "<tr><td>${_('No data ready yet')}</td></tr>";
+        tbl.innerHTML = '<tr><td>' + ${h.jshtml(_('No data ready yet'))} + '</td></tr>';
     }
 
     $('#lang_stats').append(tbl);
--- a/kallithea/tests/functional/test_changelog.py	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/tests/functional/test_changelog.py	Tue Feb 28 17:19:00 2017 +0100
@@ -21,7 +21,7 @@
         )
         response.mustcontain("""code garden""")
 
-        response.mustcontain("""var jsdata = [[[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 1, 2, 0], [0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0], [1, 1, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 2, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 3, 0], [0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 3, 0]], 0, 0, 0, 0, 0, 0], [[1, 3], [[0, 0, 2, 0], [1, 1, 3, 0]], 0, 0, 0, 0, 0, 0], [[1, 3], [[0, 0, 2, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 4, 0], [0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 5, 0], [0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 5, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 5, 0]], 0, 0, 0, 0, 0, 0], [[1, 5], [[0, 0, 2, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 6, 0], [0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 6, 0]], 0, 0, 0, 0, 0, 0], [[1, 6], [[0, 0, 2, 0], [1, 1, 6, 0]], 0, 0, 0, 0, 0, 0], [[1, 6], [[0, 0, 2, 0], [1, 1, 6, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 6, 0]], 0, 0, 0, 0, 0, 0], [[1, 6], [[0, 0, 2, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 7, 0], [0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 7, 0]], 0, 0, 0, 0, 0, 0], [[1, 7], [[0, 0, 2, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 8, 0], [0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 8, 0]], 0, 0, 0, 0, 0, 0], [[1, 8], [[0, 0, 2, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 9, 0], [0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 10, 0], [0, 0, 2, 0], [1, 2, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 10, 0], [2, 2, 9, 0]], 0, 0, 0, 0, 0, 0], [[2, 9], [[0, 0, 2, 0], [1, 1, 10, 0], [2, 1, 10, 0]], 0, 0, 0, 0, 0, 0], [[1, 10], [[0, 0, 2, 0], [1, 1, 10, 0]], 0, 0, 0, 0, 0, 0], [[1, 10], [[0, 0, 2, 0], [1, 1, 10, 0]], 0, 0, 0, 0, 0, 0], [[1, 10], [[0, 0, 2, 0], [1, 1, 10, 0]], 0, 0, 0, 0, 0, 0], [[1, 10], [[0, 0, 2, 0], [1, 1, 10, 0]], 0, 0, 0, 0, 0, 0], [[1, 10], [[0, 0, 2, 0], [1, 1, 10, 0]], 0, 0, 0, 0, 0, 0], [[1, 10], [[0, 0, 2, 0], [1, 1, 10, 0]], 0, 0, 0, 0, 0, 0], [[1, 10], [[0, 0, 2, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[1, 11], [[0, 0, 2, 0], [1, 1, 11, 0]], 1, 0, 0, 0, 0, 0], [[2, 12], [[0, 0, 2, 0], [2, 1, 12, 0]], 1, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 13, 0], [0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 13, 0]], 0, 0, 0, 0, 0, 0], [[1, 13], [[0, 0, 2, 0], [1, 1, 13, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 13, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 13, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 13, 0]], 0, 0, 0, 0, 0, 0], [[1, 13], [[0, 0, 2, 0], [1, 1, 13, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 14, 0], [0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0]];""")
+        response.mustcontain("""var jsdata = ([[[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 1, 2, 0], [0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0], [1, 1, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 2, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 3, 0], [0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 3, 0]], 0, 0, 0, 0, 0, 0], [[1, 3], [[0, 0, 2, 0], [1, 1, 3, 0]], 0, 0, 0, 0, 0, 0], [[1, 3], [[0, 0, 2, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 4, 0], [0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 5, 0], [0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 5, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 5, 0]], 0, 0, 0, 0, 0, 0], [[1, 5], [[0, 0, 2, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 6, 0], [0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 6, 0]], 0, 0, 0, 0, 0, 0], [[1, 6], [[0, 0, 2, 0], [1, 1, 6, 0]], 0, 0, 0, 0, 0, 0], [[1, 6], [[0, 0, 2, 0], [1, 1, 6, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 6, 0]], 0, 0, 0, 0, 0, 0], [[1, 6], [[0, 0, 2, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 7, 0], [0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 7, 0]], 0, 0, 0, 0, 0, 0], [[1, 7], [[0, 0, 2, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 8, 0], [0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 8, 0]], 0, 0, 0, 0, 0, 0], [[1, 8], [[0, 0, 2, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 9, 0], [0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 10, 0], [0, 0, 2, 0], [1, 2, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 10, 0], [2, 2, 9, 0]], 0, 0, 0, 0, 0, 0], [[2, 9], [[0, 0, 2, 0], [1, 1, 10, 0], [2, 1, 10, 0]], 0, 0, 0, 0, 0, 0], [[1, 10], [[0, 0, 2, 0], [1, 1, 10, 0]], 0, 0, 0, 0, 0, 0], [[1, 10], [[0, 0, 2, 0], [1, 1, 10, 0]], 0, 0, 0, 0, 0, 0], [[1, 10], [[0, 0, 2, 0], [1, 1, 10, 0]], 0, 0, 0, 0, 0, 0], [[1, 10], [[0, 0, 2, 0], [1, 1, 10, 0]], 0, 0, 0, 0, 0, 0], [[1, 10], [[0, 0, 2, 0], [1, 1, 10, 0]], 0, 0, 0, 0, 0, 0], [[1, 10], [[0, 0, 2, 0], [1, 1, 10, 0]], 0, 0, 0, 0, 0, 0], [[1, 10], [[0, 0, 2, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[1, 11], [[0, 0, 2, 0], [1, 1, 11, 0]], 1, 0, 0, 0, 0, 0], [[2, 12], [[0, 0, 2, 0], [2, 1, 12, 0]], 1, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 13, 0], [0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 13, 0]], 0, 0, 0, 0, 0, 0], [[1, 13], [[0, 0, 2, 0], [1, 1, 13, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 13, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 13, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 13, 0]], 0, 0, 0, 0, 0, 0], [[1, 13], [[0, 0, 2, 0], [1, 1, 13, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 14, 0], [0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0]]);""")
 
     def test_index_pagination_hg(self):
         self.log_user()
@@ -70,7 +70,7 @@
 
         response.mustcontain("""fixing stupid typo in context for mercurial""")
 
-        response.mustcontain("""var jsdata = [[[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 1, 2, 0], [0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0], [1, 1, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 2, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 3, 0], [0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 3, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 3, 0]], 0, 0, 0, 0, 0, 0], [[1, 3], [[0, 0, 2, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 4, 0], [0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 4, 0], [1, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 1, 5, 0], [0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 5, 0]], 0, 0, 0, 0, 0, 0], [[1, 5], [[0, 0, 4, 0], [1, 1, 5, 0]], 0, 0, 0, 0, 0, 0], [[1, 5], [[0, 0, 4, 0], [1, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 1, 6, 0], [0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 6, 0]], 0, 0, 0, 0, 0, 0], [[1, 6], [[0, 0, 4, 0], [1, 1, 6, 0]], 0, 0, 0, 0, 0, 0], [[1, 6], [[0, 0, 4, 0], [1, 1, 6, 0], [1, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 6, 0]], 0, 0, 0, 0, 0, 0], [[1, 6], [[0, 0, 4, 0], [1, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 1, 7, 0], [0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 7, 0]], 0, 0, 0, 0, 0, 0], [[1, 7], [[0, 0, 4, 0], [1, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 1, 8, 0], [0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 8, 0]], 0, 0, 0, 0, 0, 0], [[1, 8], [[0, 0, 4, 0], [1, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 1, 9, 0], [0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 9, 0]], 0, 0, 0, 0, 0, 0], [[1, 9], [[0, 0, 4, 0], [1, 0, 4, 0], [1, 1, 9, 0]], 0, 0, 0, 0, 0, 0], [[1, 9], [[0, 0, 4, 0], [1, 1, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 9, 0], [1, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 1, 10, 0], [0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 1, 11, 0], [0, 0, 9, 0], [1, 2, 10, 0]], 0, 0, 0, 0, 0, 0], [[2, 10], [[0, 0, 9, 0], [1, 1, 11, 0], [2, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0], [1, 1, 11, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0], [1, 1, 11, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0], [1, 1, 11, 0]], 0, 0, 0, 0, 0, 0], [[1, 11], [[0, 0, 9, 0], [1, 0, 9, 0], [1, 1, 11, 0]], 0, 0, 0, 0, 0, 0], [[1, 11], [[0, 0, 9, 0], [1, 1, 11, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0], [1, 1, 11, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0], [1, 1, 11, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0], [1, 1, 11, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0], [1, 1, 11, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 1, 11, 0], [0, 0, 9, 0], [1, 1, 11, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0], [1, 1, 11, 0]], 0, 0, 0, 0, 0, 0], [[1, 11], [[0, 0, 9, 0], [1, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0]];""")
+        response.mustcontain("""var jsdata = ([[[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 1, 2, 0], [0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0], [1, 1, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 2, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 3, 0], [0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 3, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 3, 0]], 0, 0, 0, 0, 0, 0], [[1, 3], [[0, 0, 2, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 4, 0], [0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 4, 0], [1, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 1, 5, 0], [0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 5, 0]], 0, 0, 0, 0, 0, 0], [[1, 5], [[0, 0, 4, 0], [1, 1, 5, 0]], 0, 0, 0, 0, 0, 0], [[1, 5], [[0, 0, 4, 0], [1, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 1, 6, 0], [0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 6, 0]], 0, 0, 0, 0, 0, 0], [[1, 6], [[0, 0, 4, 0], [1, 1, 6, 0]], 0, 0, 0, 0, 0, 0], [[1, 6], [[0, 0, 4, 0], [1, 1, 6, 0], [1, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 6, 0]], 0, 0, 0, 0, 0, 0], [[1, 6], [[0, 0, 4, 0], [1, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 1, 7, 0], [0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 7, 0]], 0, 0, 0, 0, 0, 0], [[1, 7], [[0, 0, 4, 0], [1, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 1, 8, 0], [0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 8, 0]], 0, 0, 0, 0, 0, 0], [[1, 8], [[0, 0, 4, 0], [1, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 1, 9, 0], [0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 9, 0]], 0, 0, 0, 0, 0, 0], [[1, 9], [[0, 0, 4, 0], [1, 0, 4, 0], [1, 1, 9, 0]], 0, 0, 0, 0, 0, 0], [[1, 9], [[0, 0, 4, 0], [1, 1, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 9, 0], [1, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 1, 10, 0], [0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 1, 11, 0], [0, 0, 9, 0], [1, 2, 10, 0]], 0, 0, 0, 0, 0, 0], [[2, 10], [[0, 0, 9, 0], [1, 1, 11, 0], [2, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0], [1, 1, 11, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0], [1, 1, 11, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0], [1, 1, 11, 0]], 0, 0, 0, 0, 0, 0], [[1, 11], [[0, 0, 9, 0], [1, 0, 9, 0], [1, 1, 11, 0]], 0, 0, 0, 0, 0, 0], [[1, 11], [[0, 0, 9, 0], [1, 1, 11, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0], [1, 1, 11, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0], [1, 1, 11, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0], [1, 1, 11, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0], [1, 1, 11, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 1, 11, 0], [0, 0, 9, 0], [1, 1, 11, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0], [1, 1, 11, 0]], 0, 0, 0, 0, 0, 0], [[1, 11], [[0, 0, 9, 0], [1, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0]]);""")
 
 #        response.mustcontain(
 #            """<div id="changed_total_5e204e7583b9c8e7b93a020bd036564b1e731dae" """
--- a/kallithea/tests/functional/test_home.py	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/tests/functional/test_home.py	Tue Feb 28 17:19:00 2017 +0100
@@ -20,10 +20,10 @@
         response.mustcontain('<span class="repotag">git')
 
         # html in javascript variable:
-        response.mustcontain('var data = {"totalRecords": %s' % Repository.query().count())
+        response.mustcontain('var data = ({"totalRecords": %s' % Repository.query().count())
         response.mustcontain(r'href=\"/%s\"' % HG_REPO)
 
-        response.mustcontain(r'<i class=\"icon-globe\"')
+        response.mustcontain(r'\x3ci class=\"icon-globe\"')
 
         response.mustcontain(r'\"fixes issue with having custom format for git-log\n\"')
         response.mustcontain(r'\"/%s/changeset/5f2c6ee195929b0be80749243c18121c9864a3b3\"' % GIT_REPO)
--- a/kallithea/tests/functional/test_repo_groups.py	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/tests/functional/test_repo_groups.py	Tue Feb 28 17:19:00 2017 +0100
@@ -6,7 +6,7 @@
     def test_index(self):
         self.log_user()
         response = self.app.get(url('repos_groups'))
-        response.mustcontain('{"totalRecords": 0, "sort": null, "startIndex": 0, "dir": "asc", "records": []};')
+        response.mustcontain('{"totalRecords": 0, "sort": null, "startIndex": 0, "dir": "asc", "records": []}')
 
     def test_new(self):
         self.log_user()
--- a/kallithea/tests/functional/test_summary.py	Mon Mar 06 02:23:26 2017 +0100
+++ b/kallithea/tests/functional/test_summary.py	Tue Feb 28 17:19:00 2017 +0100
@@ -146,7 +146,7 @@
             '["js", {"count": 1, "desc": ["Javascript"]}], '
             '["cfg", {"count": 1, "desc": ["Ini"]}], '
             '["ini", {"count": 1, "desc": ["Ini"]}], '
-            '["html", {"count": 1, "desc": ["EvoqueHtml", "Html"]}]];'
+            '["html", {"count": 1, "desc": ["EvoqueHtml", "Html"]}]]'
         )
 
     def test_index_statistics(self):
@@ -179,7 +179,7 @@
             '["cfg", {"count": 1, "desc": ["Ini"]}], '
             '["ini", {"count": 1, "desc": ["Ini"]}], '
             '["html", {"count": 1, "desc": ["EvoqueHtml", "Html"]}], '
-            '["bat", {"count": 1, "desc": ["Batch"]}]];'
+            '["bat", {"count": 1, "desc": ["Batch"]}]]'
         )
 
     def test_index_statistics_git(self):