changeset 4763:0c8efa0c45a1

Merge
author Weblate <noreply@weblate.org>
date Wed, 07 Jan 2015 13:37:28 +0100
parents fc8ab968fe10 (current diff) 971d9ecdcc70 (diff)
children b5d899e51ffe
files
diffstat 78 files changed, 666 insertions(+), 779 deletions(-) [+]
line wrap: on
line diff
--- a/kallithea/bin/base.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/bin/base.py	Wed Jan 07 13:37:28 2015 +0100
@@ -31,13 +31,7 @@
 import urllib2
 import pprint
 
-try:
-    from kallithea.lib.ext_json import json
-except ImportError:
-    try:
-        import simplejson as json
-    except ImportError:
-        import json
+from kallithea.lib.compat import json
 
 CONFIG_NAME = '.config/kallithea'
 FORMAT_PRETTY = 'pretty'
--- a/kallithea/bin/kallithea_api.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/bin/kallithea_api.py	Wed Jan 07 13:37:28 2015 +0100
@@ -101,7 +101,7 @@
 
     try:
         margs = dict(map(lambda s: s.split(':', 1), other))
-    except Exception:
+    except ValueError:
         sys.stderr.write('Error parsing arguments \n')
         sys.exit()
     if args.format == FORMAT_PRETTY:
--- a/kallithea/bin/kallithea_config.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/bin/kallithea_config.py	Wed Jan 07 13:37:28 2015 +0100
@@ -152,10 +152,7 @@
     if argv is None:
         argv = sys.argv
 
-    try:
-        return _run(argv)
-    except Exception:
-        raise
+    return _run(argv)
 
 
 if __name__ == '__main__':
--- a/kallithea/bin/ldap_sync.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/bin/ldap_sync.py	Wed Jan 07 13:37:28 2015 +0100
@@ -29,13 +29,7 @@
 import urllib2
 import uuid
 
-try:
-    from kallithea.lib.compat import json
-except ImportError:
-    try:
-        import simplejson as json
-    except ImportError:
-        import json
+from kallithea.lib.compat import json
 
 from ConfigParser import ConfigParser
 
--- a/kallithea/controllers/admin/admin.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/controllers/admin/admin.py	Wed Jan 07 13:37:28 2015 +0100
@@ -131,11 +131,7 @@
 
         #FILTERING
         c.search_term = request.GET.get('filter')
-        try:
-            users_log = _journal_filter(users_log, c.search_term)
-        except Exception:
-            # we want this to crash for now
-            raise
+        users_log = _journal_filter(users_log, c.search_term)
 
         users_log = users_log.order_by(UserLog.action_date.desc())
 
--- a/kallithea/controllers/admin/repo_groups.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/controllers/admin/repo_groups.py	Wed Jan 07 13:37:28 2015 +0100
@@ -146,7 +146,7 @@
                 "group_name": repo_group_name(repo_gr.group_name, children_groups),
                 "desc": repo_gr.group_description,
                 "repos": repo_count,
-                "owner": h.person(repo_gr.user.username),
+                "owner": h.person(repo_gr.user),
                 "action": repo_group_actions(repo_gr.group_id, repo_gr.group_name,
                                              repo_count)
             })
--- a/kallithea/controllers/admin/settings.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/controllers/admin/settings.py	Wed Jan 07 13:37:28 2015 +0100
@@ -208,12 +208,13 @@
 
             filesystem_repos = ScmModel().repo_scan()
             added, removed = repo2db_mapper(filesystem_repos, rm_obsolete,
-                                            install_git_hook=install_git_hooks)
-            _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
-            h.flash(_('Repositories successfully '
-                      'rescanned added: %s ; removed: %s') %
-                    (_repr(added), _repr(removed)),
-                    category='success')
+                                            install_git_hook=install_git_hooks,
+                                            user=c.authuser.username)
+            h.flash(h.literal(_('Repositories successfully rescanned. Added: %s. Removed: %s.') %
+                (', '.join(h.link_to(safe_unicode(repo_name), h.url('summary_home', repo_name=repo_name))
+                 for repo_name in added) or '-',
+                 ', '.join(h.escape(safe_unicode(repo_name)) for repo_name in removed) or '-')),
+                category='success')
             return redirect(url('admin_settings_mapping'))
 
         defaults = Setting.get_app_settings()
--- a/kallithea/controllers/admin/user_groups.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/controllers/admin/user_groups.py	Wed Jan 07 13:37:28 2015 +0100
@@ -138,16 +138,17 @@
         users_group_form = UserGroupForm()()
         try:
             form_result = users_group_form.to_python(dict(request.POST))
-            UserGroupModel().create(name=form_result['users_group_name'],
-                                    description=form_result['user_group_description'],
-                                    owner=self.authuser.user_id,
-                                    active=form_result['users_group_active'])
+            ug = UserGroupModel().create(name=form_result['users_group_name'],
+                                         description=form_result['user_group_description'],
+                                         owner=self.authuser.user_id,
+                                         active=form_result['users_group_active'])
 
             gr = form_result['users_group_name']
             action_logger(self.authuser,
                           'admin_created_users_group:%s' % gr,
                           None, self.ip_addr, self.sa)
-            h.flash(_('Created user group %s') % gr, category='success')
+            h.flash(h.literal(_('Created user group %s') % h.link_to(h.escape(gr), url('edit_users_group', id=ug.users_group_id))),
+                category='success')
             Session().commit()
         except formencode.Invalid, errors:
             return htmlfill.render(
--- a/kallithea/controllers/admin/users.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/controllers/admin/users.py	Wed Jan 07 13:37:28 2015 +0100
@@ -94,9 +94,8 @@
                 .render(user_id, username, _=_, h=h, c=c))
 
         for user in c.users_list:
-
             users_data.append({
-                "gravatar": grav_tmpl(user. email, 20),
+                "gravatar": grav_tmpl(user.email, 20),
                 "raw_name": user.username,
                 "username": username(user.user_id, user.username),
                 "firstname": user.name,
@@ -128,11 +127,11 @@
         user_form = UserForm()()
         try:
             form_result = user_form.to_python(dict(request.POST))
-            user_model.create(form_result)
+            user = user_model.create(form_result)
             usr = form_result['username']
             action_logger(self.authuser, 'admin_created_user:%s' % usr,
                           None, self.ip_addr, self.sa)
-            h.flash(_('Created user %s') % usr,
+            h.flash(h.literal(_('Created user %s') % h.link_to(h.escape(usr), url('edit_user', id=user.user_id))),
                     category='success')
             Session().commit()
         except formencode.Invalid, errors:
--- a/kallithea/controllers/changeset.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/controllers/changeset.py	Wed Jan 07 13:37:28 2015 +0100
@@ -75,10 +75,7 @@
     ig_ws_global = GET.get('ignorews')
     ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
     if ig_ws:
-        try:
-            return int(ig_ws[0].split(':')[-1])
-        except Exception:
-            pass
+        return int(ig_ws[0].split(':')[-1])
     return ig_ws_global
 
 
--- a/kallithea/controllers/error.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/controllers/error.py	Wed Jan 07 13:37:28 2015 +0100
@@ -57,20 +57,15 @@
         resp = request.environ.get('pylons.original_response')
         c.site_name = config.get('title')
 
-        log.debug('### %s ###' % resp.status)
+        log.debug('### %s ###' % resp and resp.status)
 
         e = request.environ
         c.serv_p = r'%(protocol)s://%(host)s/' \
                                     % {'protocol': e.get('wsgi.url_scheme'),
                                        'host': e.get('HTTP_HOST'), }
 
-        c.error_message = cgi.escape(request.GET.get('code', str(resp.status)))
-        c.error_explanation = self.get_error_explanation(resp.status_int)
-
-        #  redirect to when error with given seconds
-        c.redirect_time = 0
-        c.redirect_module = _('Home page')
-        c.url_redirect = "/"
+        c.error_message = resp and cgi.escape(request.GET.get('code', str(resp.status)))
+        c.error_explanation = resp and self.get_error_explanation(resp.status_int)
 
         return render('/errors/error_document.html')
 
@@ -94,7 +89,7 @@
             [400, 401, 403, 404, 500]"""
         try:
             code = int(code)
-        except Exception:
+        except ValueError:
             code = 500
 
         if code == 400:
--- a/kallithea/controllers/files.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/controllers/files.py	Wed Jan 07 13:37:28 2015 +0100
@@ -190,7 +190,7 @@
             return render('files/files_ypjax.html')
 
         # TODO: tags and bookmarks?
-        c.revision_options = [(c.changeset.raw_id, 
+        c.revision_options = [(c.changeset.raw_id,
                               _('%s at %s') % (c.changeset.branch, h.short_id(c.changeset.raw_id)))] + \
             [(n, b) for b, n in c.db_repo_scm_instance.branches.items()]
         if c.db_repo_scm_instance.closed_branches:
--- a/kallithea/controllers/journal.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/controllers/journal.py	Wed Jan 07 13:37:28 2015 +0100
@@ -27,6 +27,7 @@
 """
 
 import logging
+import traceback
 from itertools import groupby
 
 from sqlalchemy import or_
@@ -95,11 +96,7 @@
                 .options(joinedload(UserLog.user))\
                 .options(joinedload(UserLog.repository))
             #filter
-            try:
-                journal = _journal_filter(journal, c.search_term)
-            except Exception:
-                # we want this to crash for now
-                raise
+            journal = _journal_filter(journal, c.search_term)
             journal = journal.filter(filtering_criterion)\
                         .order_by(UserLog.action_date.desc())
         else:
@@ -320,6 +317,7 @@
                     Session.commit()
                     return 'ok'
                 except Exception:
+                    log.error(traceback.format_exc())
                     raise HTTPBadRequest()
 
             repo_id = request.POST.get('follows_repo_id')
@@ -330,6 +328,7 @@
                     Session.commit()
                     return 'ok'
                 except Exception:
+                    log.error(traceback.format_exc())
                     raise HTTPBadRequest()
 
         log.debug('token mismatch %s vs %s' % (cur_token, token))
--- a/kallithea/controllers/pullrequests.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/controllers/pullrequests.py	Wed Jan 07 13:37:28 2015 +0100
@@ -578,17 +578,18 @@
         if org_scm_instance.alias == 'hg' and c.a_ref_name != 'ancestor':
             if c.cs_ref_type != 'branch':
                 c.cs_branch_name = org_scm_instance.get_changeset(c.cs_ref_name).branch # use ref_type ?
-            other_branch_name = c.a_ref_name
+            c.a_branch_name = c.a_ref_name
             if c.a_ref_type != 'branch':
                 try:
-                    other_branch_name = other_scm_instance.get_changeset(c.a_ref_name).branch # use ref_type ?
+                    c.a_branch_name = other_scm_instance.get_changeset(c.a_ref_name).branch # use ref_type ?
                 except EmptyRepositoryError:
-                    other_branch_name = 'null' # not a branch name ... but close enough
+                    c.a_branch_name = 'null' # not a branch name ... but close enough
+            arevs = []
             # candidates: descendants of old head that are on the right branch
             #             and not are the old head itself ...
             #             and nothing at all if old head is a descendent of target ref name
-            if other_scm_instance._repo.revs('present(%s)::&%s', c.cs_ranges[-1].raw_id, other_branch_name):
-                c.update_msg = _('This pull request has already been merged to %s.') % other_branch_name
+            if other_scm_instance._repo.revs('present(%s)::&%s', c.cs_ranges[-1].raw_id, c.a_branch_name):
+                c.update_msg = _('This pull request has already been merged to %s.') % c.a_branch_name
             else: # look for children of PR head on source branch in org repo
                 arevs = org_scm_instance._repo.revs('%s:: & branch(%s) - %s',
                                                     revs[0], c.cs_branch_name, revs[0])
@@ -601,12 +602,11 @@
                 else:
                     c.update_msg = _('No changesets found for updating this pull request.')
 
-            revs = org_scm_instance._repo.revs('head() & not (%s::) & branch(%s) & !closed()', revs[0], c.cs_branch_name)
-            if revs:
-                c.update_msg_other = _('Note: Branch %s also contains unrelated changes, such as %s.') % (c.cs_branch_name,
-                    h.short_id(org_scm_instance.get_changeset((max(revs))).raw_id))
-            else:
-                c.update_msg_other = _('Branch %s does not contain other changes.') % c.cs_branch_name
+            brevs = org_scm_instance._repo.revs('%s - %d - %ld', c.cs_branch_name, revs[0], arevs)
+            if brevs:
+                c.update_msg_other = _('Note: Branch %s has another head: %s.') % (c.cs_branch_name,
+                    h.short_id(org_scm_instance.get_changeset((max(brevs))).raw_id))
+
         elif org_scm_instance.alias == 'git':
             c.update_msg = _("Git pull requests don't support updates yet.")
 
--- a/kallithea/controllers/summary.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/controllers/summary.py	Wed Jan 07 13:37:28 2015 +0100
@@ -118,8 +118,6 @@
                 pass
             except EmptyRepositoryError:
                 pass
-            except Exception:
-                log.error(traceback.format_exc())
 
             return readme_data, readme_file
 
--- a/kallithea/lib/auth.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/lib/auth.py	Wed Jan 07 13:37:28 2015 +0100
@@ -711,8 +711,6 @@
         sa = meta.Session
         all_perms = sa.query(Permission).all()
         config['available_permissions'] = [x.permission_name for x in all_perms]
-    except Exception:
-        log.error(traceback.format_exc())
     finally:
         meta.Session.remove()
 
@@ -1175,12 +1173,7 @@
         # dict by unicode
         repo_name = safe_unicode(repo_name)
         usr = AuthUser(user.user_id)
-        try:
-            self.user_perms = set([usr.permissions['repositories'][repo_name]])
-        except Exception:
-            log.error('Exception while accessing permissions %s' %
-                      traceback.format_exc())
-            self.user_perms = set()
+        self.user_perms = set([usr.permissions['repositories'][repo_name]])
         self.username = user.username
         self.repo_name = repo_name
         return self.check_permissions()
@@ -1318,15 +1311,8 @@
     log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
     if isinstance(allowed_ips, (tuple, list, set)):
         for ip in allowed_ips:
-            try:
-                if ipaddr.IPAddress(source_ip) in ipaddr.IPNetwork(ip):
-                    log.debug('IP %s is network %s' %
-                              (ipaddr.IPAddress(source_ip), ipaddr.IPNetwork(ip)))
-                    return True
-                # for any case we cannot determine the IP, don't crash just
-                # skip it and log as error, we want to say forbidden still when
-                # sending bad IP
-            except Exception:
-                log.error(traceback.format_exc())
-                continue
+            if ipaddr.IPAddress(source_ip) in ipaddr.IPNetwork(ip):
+                log.debug('IP %s is network %s' %
+                          (ipaddr.IPAddress(source_ip), ipaddr.IPNetwork(ip)))
+                return True
     return False
--- a/kallithea/lib/auth_modules/__init__.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/lib/auth_modules/__init__.py	Wed Jan 07 13:37:28 2015 +0100
@@ -293,12 +293,8 @@
             Session().flush()
             # enforce user is just in given groups, all of them has to be ones
             # created from plugins. We store this info in _group_data JSON field
-            try:
-                groups = auth['groups'] or []
-                UserGroupModel().enforce_groups(user, groups, self.name)
-            except Exception:
-                # for any reason group syncing fails, we should proceed with login
-                log.error(traceback.format_exc())
+            groups = auth['groups'] or []
+            UserGroupModel().enforce_groups(user, groups, self.name)
             Session().commit()
         return auth
 
@@ -417,6 +413,7 @@
             return plugin_user
 
         # we failed to Auth because .auth() method didn't return proper the user
-        log.warning("User `%s` failed to authenticate against %s"
-                    % (username, plugin.__module__))
+        if username:
+            log.warning("User `%s` failed to authenticate against %s"
+                        % (username, plugin.__module__))
     return None
--- a/kallithea/lib/auth_modules/auth_pam.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/lib/auth_modules/auth_pam.py	Wed Jan 07 13:37:28 2015 +0100
@@ -132,7 +132,7 @@
                 user_attrs["firstname"] = match.group('first_name')
                 user_attrs["lastname"] = match.group('last_name')
         except Exception:
-            log.warning("Cannot extract additional info for PAM user")
+            log.warning("Cannot extract additional info for PAM user %s", username)
             pass
 
         log.debug("pamuser: \n%s" % formatted_json(user_attrs))
--- a/kallithea/lib/celerylib/tasks.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/lib/celerylib/tasks.py	Wed Jan 07 13:37:28 2015 +0100
@@ -59,13 +59,10 @@
 def get_logger(cls):
     if CELERY_ON:
         try:
-            log = cls.get_logger()
-        except Exception:
-            log = logging.getLogger(__name__)
-    else:
-        log = logging.getLogger(__name__)
-
-    return log
+            return cls.get_logger()
+        except AttributeError:
+            pass
+    return logging.getLogger(__name__)
 
 
 @task(ignore_result=True)
--- a/kallithea/lib/db_manage.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/lib/db_manage.py	Wed Jan 07 13:37:28 2015 +0100
@@ -190,12 +190,8 @@
 
         paths.ui_value = paths.ui_value.replace('*', '')
 
-        try:
-            self.sa.add(paths)
-            self.sa.commit()
-        except Exception:
-            self.sa.rollback()
-            raise
+        self.sa.add(paths)
+        self.sa.commit()
 
     def fix_default_user(self):
         """
@@ -210,12 +206,8 @@
         def_user.lastname = 'User'
         def_user.email = 'anonymous@kallithea-scm.org'
 
-        try:
-            self.sa.add(def_user)
-            self.sa.commit()
-        except Exception:
-            self.sa.rollback()
-            raise
+        self.sa.add(def_user)
+        self.sa.commit()
 
     def fix_settings(self):
         """
@@ -224,12 +216,8 @@
 
         hgsettings3 = Setting('ga_code', '')
 
-        try:
-            self.sa.add(hgsettings3)
-            self.sa.commit()
-        except Exception:
-            self.sa.rollback()
-            raise
+        self.sa.add(hgsettings3)
+        self.sa.commit()
 
     def admin_prompt(self, second=False):
         if not self.tests:
@@ -286,7 +274,6 @@
     def create_ui_settings(self, repo_store_path):
         """
         Creates ui settings, fills out hooks
-        and disables dotencode
         """
 
         #HOOKS
--- a/kallithea/lib/dbmigrate/schema/db_1_2_0.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/lib/dbmigrate/schema/db_1_2_0.py	Wed Jan 07 13:37:28 2015 +0100
@@ -711,9 +711,6 @@
 
             repo = backend(safe_str(repo_full_path), create=False,
                            baseui=self._ui)
-            # skip hidden web repository
-            if repo._get_hidden():
-                return
         else:
             repo = backend(repo_full_path, create=False)
 
--- a/kallithea/lib/dbmigrate/schema/db_1_3_0.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/lib/dbmigrate/schema/db_1_3_0.py	Wed Jan 07 13:37:28 2015 +0100
@@ -729,9 +729,6 @@
 
             repo = backend(safe_str(repo_full_path), create=False,
                            baseui=self._ui)
-            # skip hidden web repository
-            if repo._get_hidden():
-                return
         else:
             repo = backend(repo_full_path, create=False)
 
--- a/kallithea/lib/dbmigrate/schema/db_1_4_0.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/lib/dbmigrate/schema/db_1_4_0.py	Wed Jan 07 13:37:28 2015 +0100
@@ -990,9 +990,6 @@
 
             repo = backend(safe_str(repo_full_path), create=False,
                            baseui=self._ui)
-            # skip hidden web repository
-            if repo._get_hidden():
-                return
         else:
             repo = backend(repo_full_path, create=False)
 
--- a/kallithea/lib/dbmigrate/schema/db_1_5_0.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/lib/dbmigrate/schema/db_1_5_0.py	Wed Jan 07 13:37:28 2015 +0100
@@ -1009,9 +1009,6 @@
 
             repo = backend(safe_str(repo_full_path), create=False,
                            baseui=self._ui)
-            # skip hidden web repository
-            if repo._get_hidden():
-                return
         else:
             repo = backend(repo_full_path, create=False)
 
--- a/kallithea/lib/dbmigrate/schema/db_1_5_2.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/lib/dbmigrate/schema/db_1_5_2.py	Wed Jan 07 13:37:28 2015 +0100
@@ -1130,9 +1130,6 @@
 
             repo = backend(safe_str(repo_full_path), create=False,
                            baseui=self._ui)
-            # skip hidden web repository
-            if repo._get_hidden():
-                return
         else:
             repo = backend(repo_full_path, create=False)
 
--- a/kallithea/lib/dbmigrate/schema/db_1_6_0.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/lib/dbmigrate/schema/db_1_6_0.py	Wed Jan 07 13:37:28 2015 +0100
@@ -1206,9 +1206,6 @@
 
             repo = backend(safe_str(repo_full_path), create=False,
                            baseui=self._ui)
-            # skip hidden web repository
-            if repo._get_hidden():
-                return
         else:
             repo = backend(repo_full_path, create=False)
 
--- a/kallithea/lib/dbmigrate/schema/db_1_7_0.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/lib/dbmigrate/schema/db_1_7_0.py	Wed Jan 07 13:37:28 2015 +0100
@@ -1223,9 +1223,6 @@
 
             repo = backend(safe_str(repo_full_path), create=False,
                            baseui=self._ui)
-            # skip hidden web repository
-            if repo._get_hidden():
-                return
         else:
             repo = backend(repo_full_path, create=False)
 
--- a/kallithea/lib/dbmigrate/schema/db_1_8_0.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/lib/dbmigrate/schema/db_1_8_0.py	Wed Jan 07 13:37:28 2015 +0100
@@ -1253,9 +1253,6 @@
 
             repo = backend(safe_str(repo_full_path), create=False,
                            baseui=self._ui)
-            # skip hidden web repository
-            if repo._get_hidden():
-                return
         else:
             repo = backend(repo_full_path, create=False)
 
--- a/kallithea/lib/dbmigrate/schema/db_2_0_0.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/lib/dbmigrate/schema/db_2_0_0.py	Wed Jan 07 13:37:28 2015 +0100
@@ -1310,9 +1310,6 @@
 
             repo = backend(safe_str(repo_full_path), create=False,
                            baseui=self._ui)
-            # skip hidden web repository
-            if repo._get_hidden():
-                return
         else:
             repo = backend(repo_full_path, create=False)
 
--- a/kallithea/lib/dbmigrate/schema/db_2_0_1.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/lib/dbmigrate/schema/db_2_0_1.py	Wed Jan 07 13:37:28 2015 +0100
@@ -1309,9 +1309,6 @@
 
             repo = backend(safe_str(repo_full_path), create=False,
                            baseui=self._ui)
-            # skip hidden web repository
-            if repo._get_hidden():
-                return
         else:
             repo = backend(repo_full_path, create=False)
 
--- a/kallithea/lib/dbmigrate/schema/db_2_0_2.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/lib/dbmigrate/schema/db_2_0_2.py	Wed Jan 07 13:37:28 2015 +0100
@@ -1329,9 +1329,6 @@
 
             repo = backend(safe_str(repo_full_path), create=False,
                            baseui=self._ui)
-            # skip hidden web repository
-            if repo._get_hidden():
-                return
         else:
             repo = backend(repo_full_path, create=False)
 
--- a/kallithea/lib/dbmigrate/schema/db_2_1_0.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/lib/dbmigrate/schema/db_2_1_0.py	Wed Jan 07 13:37:28 2015 +0100
@@ -1368,9 +1368,6 @@
 
             repo = backend(safe_str(repo_full_path), create=False,
                            baseui=self._ui)
-            # skip hidden web repository
-            if repo._get_hidden():
-                return
         else:
             repo = backend(repo_full_path, create=False)
 
--- a/kallithea/lib/dbmigrate/schema/db_2_2_0.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/lib/dbmigrate/schema/db_2_2_0.py	Wed Jan 07 13:37:28 2015 +0100
@@ -1414,9 +1414,6 @@
 
             repo = backend(safe_str(repo_full_path), create=False,
                            baseui=self._ui)
-            # skip hidden web repository
-            if repo._get_hidden():
-                return
         else:
             repo = backend(repo_full_path, create=False)
 
--- a/kallithea/lib/dbmigrate/schema/db_2_2_3.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/lib/dbmigrate/schema/db_2_2_3.py	Wed Jan 07 13:37:28 2015 +0100
@@ -1441,9 +1441,6 @@
 
             repo = backend(safe_str(repo_full_path), create=False,
                            baseui=self._ui)
-            # skip hidden web repository
-            if repo._get_hidden():
-                return
         else:
             repo = backend(repo_full_path, create=False)
 
--- a/kallithea/lib/diffs.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/lib/diffs.py	Wed Jan 07 13:37:28 2015 +0100
@@ -196,7 +196,7 @@
     #used for inline highlighter word split
     _token_re = re.compile(r'()(&gt;|&lt;|&amp;|<u>\t</u>| <i></i>|\W+?)')
 
-    _escape_re = re.compile(r'(&)|(<)|(>)|(\t)|(?<=.)( \n| $)')
+    _escape_re = re.compile(r'(&)|(<)|(>)|(\t)|(\r)|(?<=.)( \n| $)')
 
 
     def __init__(self, diff, vcs='hg', format='gitdiff', diff_limit=None):
@@ -264,6 +264,8 @@
             if groups[3]:
                 return '<u>\t</u>'
             if groups[4]:
+                return '<u class="cr"></u>'
+            if groups[5]:
                 return ' <i></i>'
             assert False
 
--- a/kallithea/lib/ext_json.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/lib/ext_json.py	Wed Jan 07 13:37:28 2015 +0100
@@ -1,14 +1,20 @@
+"""
+Extended JSON encoder for json
+
+json.org do not specify how date time can be represented - monkeypatch it to do something.
+"""
+
 import datetime
 import functools
 import decimal
-import imp
+import json # is re-exported after monkey patching
 
-__all__ = ['json', 'simplejson', 'stdlibjson']
+__all__ = ['json']
 
 
-def _is_aware(value):
+def _is_tz_aware(value):
     """
-    Determines if a given datetime.time is aware.
+    Determines if a given datetime.time is timezone aware.
 
     The logic is described in Python's docs:
     http://docs.python.org/library/datetime.html#datetime.tzinfo
@@ -16,7 +22,6 @@
     return (value.tzinfo is not None
             and value.tzinfo.utcoffset(value) is not None)
 
-
 def _obj_dump(obj):
     """
     Custom function for dumping objects to JSON, if obj has __json__ attribute
@@ -41,7 +46,7 @@
     elif isinstance(obj, decimal.Decimal):
         return str(obj)
     elif isinstance(obj, datetime.time):
-        if _is_aware(obj):
+        if _is_tz_aware(obj):
             raise ValueError("JSON can't represent timezone-aware times.")
         r = obj.isoformat()
         if obj.microsecond:
@@ -58,65 +63,15 @@
         raise NotImplementedError
 
 
-# Import simplejson
-try:
-    # import simplejson initially
-    _sj = imp.load_module('_sj', *imp.find_module('simplejson'))
-
-    def extended_encode(obj):
+class ExtendedEncoder(json.JSONEncoder):
+    def default(self, obj):
         try:
             return _obj_dump(obj)
         except NotImplementedError:
             pass
         raise TypeError("%r is not JSON serializable" % (obj,))
-    # we handle decimals our own it makes unified behavior of json vs
-    # simplejson
-    sj_version = [int(x) for x in _sj.__version__.split('.')]
-    major, minor = sj_version[0], sj_version[1]
-    if major < 2 or (major == 2 and minor < 1):
-        # simplejson < 2.1 doesnt support use_decimal
-        _sj.dumps = functools.partial(_sj.dumps,
-                                             default=extended_encode)
-        _sj.dump = functools.partial(_sj.dump,
-                                            default=extended_encode)
-    else:
-        _sj.dumps = functools.partial(_sj.dumps,
-                                             default=extended_encode,
-                                             use_decimal=False)
-        _sj.dump = functools.partial(_sj.dump,
-                                            default=extended_encode,
-                                            use_decimal=False)
-    simplejson = _sj
-
-except ImportError:
-    # no simplejson set it to None
-    simplejson = None
 
 
-try:
-    # simplejson not found try out regular json module
-    _json = imp.load_module('_json', *imp.find_module('json'))
-
-    # extended JSON encoder for json
-    class ExtendedEncoder(_json.JSONEncoder):
-        def default(self, obj):
-            try:
-                return _obj_dump(obj)
-            except NotImplementedError:
-                pass
-            raise TypeError("%r is not JSON serializable" % (obj,))
-    # monkey-patch JSON encoder to use extended version
-    _json.dumps = functools.partial(_json.dumps, cls=ExtendedEncoder)
-    _json.dump = functools.partial(_json.dump, cls=ExtendedEncoder)
-
-    stdlibjson = _json
-except ImportError:
-    stdlibjson = None
-
-# set all available json modules
-if simplejson:
-    json = _sj
-elif stdlibjson:
-    json = _json
-else:
-    raise ImportError('Could not find any json modules')
+# monkey-patch and export JSON encoder to use custom encoding method
+json.dumps = functools.partial(json.dumps, cls=ExtendedEncoder)
+json.dump = functools.partial(json.dump, cls=ExtendedEncoder)
--- a/kallithea/lib/helpers.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/lib/helpers.py	Wed Jan 07 13:37:28 2015 +0100
@@ -519,6 +519,8 @@
 
 
 def person(author, show_attr="username"):
+    """Find the user identified by 'author', return one of the users attributes,
+    default to the username attribute, None if there is no user"""
     # attr to return from fetched user
     person_getter = lambda usr: getattr(usr, show_attr)
 
@@ -1281,8 +1283,18 @@
          'rev': rev,
         }
 
-    return re.sub(r'(?:^|(?<=\s))([0-9a-fA-F]{12,40})(?=$|\s|[.,:])', url_func, text_)
+    return re.sub(r'(?:^|(?<=[\s(),]))([0-9a-fA-F]{12,40})(?=$|\s|[.,:()])', url_func, text_)
 
+def linkify_others(t, l):
+    urls = re.compile(r'(\<a.*?\<\/a\>)',)
+    links = []
+    for e in urls.split(t):
+        if not urls.match(e):
+            links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
+        else:
+            links.append(e)
+
+    return ''.join(links)
 
 def urlify_commit(text_, repository, link_=None):
     """
@@ -1294,91 +1306,77 @@
     :param repository:
     :param link_: changeset link
     """
-    import traceback
-    from pylons import url  # doh, we need to re-import url to mock it later
-
     def escaper(string):
         return string.replace('<', '&lt;').replace('>', '&gt;')
 
-    def linkify_others(t, l):
-        urls = re.compile(r'(\<a.*?\<\/a\>)',)
-        links = []
-        for e in urls.split(t):
-            if not urls.match(e):
-                links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
-            else:
-                links.append(e)
-
-        return ''.join(links)
-
     # urlify changesets - extract revisions and make link out of them
     newtext = urlify_changesets(escaper(text_), repository)
 
     # extract http/https links and make them real urls
     newtext = urlify_text(newtext, safe=False)
 
-    try:
-        from kallithea import CONFIG
-        conf = CONFIG
+    newtext = urlify_issues(newtext, repository, link_)
+
+    return literal(newtext)
+
+def urlify_issues(newtext, repository, link_=None):
+    from kallithea import CONFIG as conf
 
-        # allow multiple issue servers to be used
-        valid_indices = [
-            x.group(1)
-            for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
-            if x and 'issue_server_link%s' % x.group(1) in conf
-            and 'issue_prefix%s' % x.group(1) in conf
-        ]
+    # allow multiple issue servers to be used
+    valid_indices = [
+        x.group(1)
+        for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
+        if x and 'issue_server_link%s' % x.group(1) in conf
+        and 'issue_prefix%s' % x.group(1) in conf
+    ]
 
+    if valid_indices:
         log.debug('found issue server suffixes `%s` during valuation of: %s'
                   % (','.join(valid_indices), newtext))
 
-        for pattern_index in valid_indices:
-            ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
-            ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
-            ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
-
-            log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
-                      % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
-                         ISSUE_PREFIX))
-
-            URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
+    for pattern_index in valid_indices:
+        ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
+        ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
+        ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
 
-            def url_func(match_obj):
-                pref = ''
-                if match_obj.group().startswith(' '):
-                    pref = ' '
+        log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
+                  % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
+                     ISSUE_PREFIX))
 
-                issue_id = ''.join(match_obj.groups())
-                issue_url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
-                if repository:
-                    issue_url = issue_url.replace('{repo}', repository)
-                    repo_name = repository.split(URL_SEP)[-1]
-                    issue_url = issue_url.replace('{repo_name}', repo_name)
+        URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
+
+        def url_func(match_obj):
+            pref = ''
+            if match_obj.group().startswith(' '):
+                pref = ' '
 
-                return (
-                    '%(pref)s<a class="%(cls)s" href="%(url)s">'
-                    '%(issue-prefix)s%(id-repr)s'
-                    '</a>'
-                    ) % {
-                     'pref': pref,
-                     'cls': 'issue-tracker-link',
-                     'url': issue_url,
-                     'id-repr': issue_id,
-                     'issue-prefix': ISSUE_PREFIX,
-                     'serv': ISSUE_SERVER_LNK,
-                    }
-            newtext = URL_PAT.sub(url_func, newtext)
-            log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
+            issue_id = ''.join(match_obj.groups())
+            issue_url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
+            if repository:
+                issue_url = issue_url.replace('{repo}', repository)
+                repo_name = repository.split(URL_SEP)[-1]
+                issue_url = issue_url.replace('{repo_name}', repo_name)
 
-        # if we actually did something above
-        if link_:
-            # wrap not links into final link => link_
-            newtext = linkify_others(newtext, link_)
-    except Exception:
-        log.error(traceback.format_exc())
-        pass
+            return (
+                '%(pref)s<a class="%(cls)s" href="%(url)s">'
+                '%(issue-prefix)s%(id-repr)s'
+                '</a>'
+                ) % {
+                 'pref': pref,
+                 'cls': 'issue-tracker-link',
+                 'url': issue_url,
+                 'id-repr': issue_id,
+                 'issue-prefix': ISSUE_PREFIX,
+                 'serv': ISSUE_SERVER_LNK,
+                }
+        newtext = URL_PAT.sub(url_func, newtext)
+        log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
 
-    return literal(newtext)
+    # if we actually did something above
+    if link_:
+        # wrap not links into final link => link_
+        newtext = linkify_others(newtext, link_)
+    return newtext
 
 
 def rst(source):
--- a/kallithea/lib/paster_commands/update_repoinfo.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/lib/paster_commands/update_repoinfo.py	Wed Jan 07 13:37:28 2015 +0100
@@ -12,10 +12,10 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 """
-kallithea.lib.paster_commands.make_rcextensions
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+kallithea.lib.paster_commands.update_repoinfo
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-uodate-repoinfo paster command for Kallithea
+update-repoinfo paster command for Kallithea
 
 This file was forked by the Kallithea project in July 2014.
 Original author and date, and relevant copyright and licensing information is below:
@@ -65,8 +65,8 @@
                                if self.options.repo_update_list else None
 
         if repo_update_list:
-            repo_list = Repository.query()\
-                .filter(Repository.repo_name.in_(repo_update_list))
+            repo_list = list(Repository.query()\
+                .filter(Repository.repo_name.in_(repo_update_list)))
         else:
             repo_list = Repository.getAll()
         RepoModel.update_repoinfo(repositories=repo_list)
@@ -75,7 +75,7 @@
         if self.options.invalidate_cache:
             for r in repo_list:
                 r.set_invalidate()
-        log.info('Updated cache for %s repositories' % (len(repo_list)))
+        print 'Updated cache for %s repositories' % (len(repo_list))
 
     def update_parser(self):
         self.parser.add_option('--update-only',
--- a/kallithea/lib/utils.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/lib/utils.py	Wed Jan 07 13:37:28 2015 +0100
@@ -120,16 +120,10 @@
 
 def get_user_group_slug(request):
     _group = request.environ['pylons.routes_dict'].get('id')
-    try:
-        _group = UserGroup.get(_group)
-        if _group:
-            _group = _group.users_group_name
-    except Exception:
-        log.debug(traceback.format_exc())
-        #catch all failures here
-        pass
-
-    return _group
+    _group = UserGroup.get(_group)
+    if _group:
+        return _group.users_group_name
+    return None
 
 
 def _extract_id_from_repo_name(repo_name):
@@ -147,15 +141,14 @@
     :param repo_name:
     :return: repo_name if matched else None
     """
-    try:
-        _repo_id = _extract_id_from_repo_name(repo_name)
-        if _repo_id:
-            from kallithea.model.db import Repository
-            return Repository.get(_repo_id).repo_name
-    except Exception:
-        log.debug('Failed to extract repo_name from URL %s' % (
-                  traceback.format_exc()))
-        return
+    _repo_id = _extract_id_from_repo_name(repo_name)
+    if _repo_id:
+        from kallithea.model.db import Repository
+        repo = Repository.get(_repo_id)
+        if repo:
+            # TODO: return repo instead of reponame? or would that be a layering violation?
+            return repo.repo_name
+    return None
 
 
 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
@@ -180,43 +173,39 @@
     if not ipaddr:
         ipaddr = getattr(get_current_authuser(), 'ip_addr', '')
 
-    try:
-        if getattr(user, 'user_id', None):
-            user_obj = User.get(user.user_id)
-        elif isinstance(user, basestring):
-            user_obj = User.get_by_username(user)
-        else:
-            raise Exception('You have to provide a user object or a username')
+    if getattr(user, 'user_id', None):
+        user_obj = User.get(user.user_id)
+    elif isinstance(user, basestring):
+        user_obj = User.get_by_username(user)
+    else:
+        raise Exception('You have to provide a user object or a username')
 
-        if getattr(repo, 'repo_id', None):
-            repo_obj = Repository.get(repo.repo_id)
-            repo_name = repo_obj.repo_name
-        elif isinstance(repo, basestring):
-            repo_name = repo.lstrip('/')
-            repo_obj = Repository.get_by_repo_name(repo_name)
-        else:
-            repo_obj = None
-            repo_name = ''
+    if getattr(repo, 'repo_id', None):
+        repo_obj = Repository.get(repo.repo_id)
+        repo_name = repo_obj.repo_name
+    elif isinstance(repo, basestring):
+        repo_name = repo.lstrip('/')
+        repo_obj = Repository.get_by_repo_name(repo_name)
+    else:
+        repo_obj = None
+        repo_name = ''
 
-        user_log = UserLog()
-        user_log.user_id = user_obj.user_id
-        user_log.username = user_obj.username
-        user_log.action = safe_unicode(action)
+    user_log = UserLog()
+    user_log.user_id = user_obj.user_id
+    user_log.username = user_obj.username
+    user_log.action = safe_unicode(action)
 
-        user_log.repository = repo_obj
-        user_log.repository_name = repo_name
+    user_log.repository = repo_obj
+    user_log.repository_name = repo_name
 
-        user_log.action_date = datetime.datetime.now()
-        user_log.user_ip = ipaddr
-        sa.add(user_log)
+    user_log.action_date = datetime.datetime.now()
+    user_log.user_ip = ipaddr
+    sa.add(user_log)
 
-        log.info('Logging action:%s on %s by user:%s ip:%s' %
-                 (action, safe_unicode(repo), user_obj, ipaddr))
-        if commit:
-            sa.commit()
-    except Exception:
-        log.error(traceback.format_exc())
-        raise
+    log.info('Logging action:%s on %s by user:%s ip:%s' %
+             (action, safe_unicode(repo), user_obj, ipaddr))
+    if commit:
+        sa.commit()
 
 
 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
@@ -373,9 +362,7 @@
 
     elif read_from == 'db':
         sa = meta.Session()
-        ret = sa.query(Ui)\
-            .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
-            .all()
+        ret = sa.query(Ui).all()
 
         hg_ui = ret
         for ui_ in hg_ui:
@@ -471,7 +458,7 @@
 
 
 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
-                   install_git_hook=False):
+                   install_git_hook=False, user=None):
     """
     maps all repos given in initial_repo_list, non existing repositories
     are created, if remove_obsolete is True it also check for db entries
@@ -486,7 +473,8 @@
     from kallithea.model.scm import ScmModel
     sa = meta.Session()
     repo_model = RepoModel()
-    user = User.get_first_admin()
+    if user is None:
+        user = User.get_first_admin()
     added = []
 
     ##creation defaults
@@ -815,7 +803,7 @@
         ver = '.'.join(ver.split('.')[:3])
     try:
         _ver = StrictVersion(ver)
-    except Exception:
+    except ValueError:
         _ver = StrictVersion('0.0.0')
         stderr = traceback.format_exc()
 
--- a/kallithea/lib/utils2.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/lib/utils2.py	Wed Jan 07 13:37:28 2015 +0100
@@ -32,7 +32,6 @@
 import time
 import uuid
 import datetime
-import traceback
 import webob
 import urllib
 import urlobject
@@ -409,6 +408,18 @@
         deltas['month'] += 12
         deltas['year'] -= 1
 
+    # In short version, we want nicer handling of ages of more than a year
+    if show_short_version:
+        if deltas['year'] == 1:
+            # ages between 1 and 2 years: show as months
+            deltas['month'] += 12
+            deltas['year'] = 0
+        if deltas['year'] >= 2:
+            # ages 2+ years: round
+            if deltas['month'] > 6:
+                deltas['year'] += 1
+                deltas['month'] = 0
+
     # Format the result
     fmt_funcs = {
         'year': lambda d: ungettext(u'%d year', '%d years', d) % d,
@@ -596,14 +607,14 @@
 
 
 def obfuscate_url_pw(engine):
-    _url = engine or ''
     from sqlalchemy.engine import url as sa_url
+    from sqlalchemy.exc import ArgumentError
     try:
-        _url = sa_url.make_url(engine)
-        if _url.password:
-            _url.password = 'XXXXX'
-    except Exception:
-        pass
+        _url = sa_url.make_url(engine or '')
+    except ArgumentError:
+        return engine
+    if _url.password:
+        _url.password = 'XXXXX'
     return str(_url)
 
 
@@ -622,9 +633,7 @@
 
     try:
         rc_extras = json.loads(env['KALLITHEA_EXTRAS'])
-    except Exception:
-        print os.environ
-        print >> sys.stderr, traceback.format_exc()
+    except KeyError:
         rc_extras = {}
 
     try:
--- a/kallithea/lib/vcs/backends/git/repository.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/lib/vcs/backends/git/repository.py	Wed Jan 07 13:37:28 2015 +0100
@@ -294,7 +294,7 @@
             or isinstance(revision, int) or is_null(revision)):
             try:
                 revision = self.revisions[int(revision)]
-            except Exception:
+            except IndexError:
                 msg = ("Revision %s does not exist for %s" % (revision, self))
                 raise ChangesetDoesNotExistError(msg)
 
--- a/kallithea/lib/vcs/backends/hg/repository.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/lib/vcs/backends/hg/repository.py	Wed Jan 07 13:37:28 2015 +0100
@@ -121,34 +121,16 @@
         if self._empty:
             return {}
 
-        def _branchtags(localrepo):
-            """
-            Patched version of mercurial branchtags to not return the closed
-            branches
-
-            :param localrepo: locarepository instance
-            """
+        bt = OrderedDict()
+        for bn, _heads, tip, isclosed in sorted(self._repo.branchmap().iterbranches()):
+            if isclosed:
+                if closed:
+                    bt[safe_unicode(bn)] = hex(tip)
+            else:
+                if normal:
+                    bt[safe_unicode(bn)] = hex(tip)
 
-            bt = {}
-            bt_closed = {}
-            for bn, heads in localrepo.branchmap().iteritems():
-                tip = heads[-1]
-                if 'close' in localrepo.changelog.read(tip)[5]:
-                    bt_closed[bn] = tip
-                else:
-                    bt[bn] = tip
-
-            if not normal:
-                return bt_closed
-            if closed:
-                bt.update(bt_closed)
-            return bt
-
-        sortkey = lambda ctx: ctx[0]  # sort by name
-        _branches = [(safe_unicode(n), hex(h),) for n, h in
-                     _branchtags(self._repo).items()]
-
-        return OrderedDict(sorted(_branches, key=sortkey, reverse=False))
+        return bt
 
     @LazyProperty
     def tags(self):
@@ -362,13 +344,8 @@
                 opts = {}
                 if not update_after_clone:
                     opts.update({'noupdate': True})
-                try:
-                    MercurialRepository._check_url(url, self.baseui)
-                    clone(self.baseui, url, self.path, **opts)
-#                except urllib2.URLError:
-#                    raise Abort("Got HTTP 404 error")
-                except Exception:
-                    raise
+                MercurialRepository._check_url(url, self.baseui)
+                clone(self.baseui, url, self.path, **opts)
 
                 # Don't try to create if we've already cloned repo
                 create = False
@@ -417,9 +394,6 @@
             else:
                 return os.stat(st_path).st_mtime
 
-    def _get_hidden(self):
-        return self._repo.ui.configbool("web", "hidden", untrusted=True)
-
     def _get_revision(self, revision):
         """
         Gets an ID revision given as str. This will always return a fill
--- a/kallithea/lib/vcs/nodes.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/lib/vcs/nodes.py	Wed Jan 07 13:37:28 2015 +0100
@@ -340,9 +340,9 @@
 
                 #try with pygments
                 try:
-                    from pygments.lexers import get_lexer_for_filename
-                    mt = get_lexer_for_filename(self.name).mimetypes
-                except Exception:
+                    from pygments import lexers
+                    mt = lexers.get_lexer_for_filename(self.name).mimetypes
+                except lexers.ClassNotFound:
                     mt = None
 
                 if mt:
--- a/kallithea/lib/vcs/subprocessio.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/lib/vcs/subprocessio.py	Wed Jan 07 13:37:28 2015 +0100
@@ -45,15 +45,9 @@
         else:  # can be either file pointer or file-like
             if type(source) in (int, long):  # file pointer it is
                 ## converting file descriptor (int) stdin into file-like
-                try:
-                    source = os.fdopen(source, 'rb', 16384)
-                except Exception:
-                    pass
+                source = os.fdopen(source, 'rb', 16384)
             # let's see if source is file-like by now
-            try:
-                filelike = source.read
-            except Exception:
-                pass
+            filelike = hasattr(source, 'read')
         if not filelike and not self.bytes:
             raise TypeError("StreamFeeder's source object must be a readable "
                             "file-like, a file descriptor, or a string-like.")
--- a/kallithea/lib/vcs/utils/__init__.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/lib/vcs/utils/__init__.py	Wed Jan 07 13:37:28 2015 +0100
@@ -158,6 +158,8 @@
 
     Regex taken from http://www.regular-expressions.info/email.html
     """
+    if not author:
+        return ''
     import re
     r = author.find('>')
     l = author.find('<')
@@ -180,9 +182,9 @@
     It'll try to find an email in the author string and just cut it off
     to get the username
     """
-
+    if not author:
+        return ''
     if not '@' in author:
         return author
-    else:
-        return author.replace(author_email(author), '').replace('<', '')\
-            .replace('>', '').strip()
+    return author.replace(author_email(author), '').replace('<', '')\
+        .replace('>', '').strip()
--- a/kallithea/model/api_key.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/model/api_key.py	Wed Jan 07 13:37:28 2015 +0100
@@ -28,7 +28,6 @@
 from __future__ import with_statement
 import time
 import logging
-import traceback
 from sqlalchemy import or_
 
 from kallithea.lib.utils2 import generate_api_key
@@ -71,11 +70,7 @@
             api_key = api_key.filter(UserApiKeys.user_id == user.user_id)
 
         api_key = api_key.scalar()
-        try:
-            Session().delete(api_key)
-        except Exception:
-            log.error(traceback.format_exc())
-            raise
+        Session().delete(api_key)
 
     def get_api_keys(self, user, show_expired=True):
         user = self._get_user(user)
--- a/kallithea/model/changeset_status.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/model/changeset_status.py	Wed Jan 07 13:37:28 2015 +0100
@@ -28,6 +28,7 @@
 
 import logging
 from collections import  defaultdict
+from sqlalchemy.orm import joinedload
 
 from kallithea.model import BaseModel
 from kallithea.model.db import ChangesetStatus, PullRequest
@@ -103,6 +104,7 @@
                      with_revisions=False):
         q = self._get_status_query(repo, revision, pull_request,
                                    with_revisions)
+        q = q.options(joinedload('author'))
         return q.all()
 
     def get_status(self, repo, revision=None, pull_request=None, as_str=True):
--- a/kallithea/model/db.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/model/db.py	Wed Jan 07 13:37:28 2015 +0100
@@ -421,6 +421,8 @@
     user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
 
     repositories = relationship('Repository')
+    repo_groups = relationship('RepoGroup')
+    user_groups = relationship('UserGroup')
     user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
     followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
 
@@ -793,7 +795,7 @@
     created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
     _group_data = Column("group_data", LargeBinary(), nullable=True)  # JSON data
 
-    members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
+    members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined")
     users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
     users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
     users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
@@ -971,18 +973,18 @@
                              primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
                              cascade='all')
     extra_fields = relationship('RepositoryField',
-                                cascade="all, delete, delete-orphan")
+                                cascade="all, delete-orphan")
 
     logs = relationship('UserLog')
-    comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
+    comments = relationship('ChangesetComment', cascade="all, delete-orphan")
 
     pull_requests_org = relationship('PullRequest',
                     primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
-                    cascade="all, delete, delete-orphan")
+                    cascade="all, delete-orphan")
 
     pull_requests_other = relationship('PullRequest',
                     primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
-                    cascade="all, delete, delete-orphan")
+                    cascade="all, delete-orphan")
 
     def __unicode__(self):
         return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
@@ -1266,7 +1268,7 @@
             try:
                 from pylons import tmpl_context as c
                 uri_tmpl = c.clone_uri_tmpl
-            except Exception:
+            except AttributeError:
                 # in any case if we call this outside of request context,
                 # ie, not having tmpl_context set up
                 pass
@@ -1362,7 +1364,8 @@
 
     def statuses(self, revisions):
         """
-        Returns statuses for this repository
+        Returns statuses for this repository.
+        PRs without any votes do _not_ show up as unreviewed.
 
         :param revisions: list of revisions to get statuses for
         """
@@ -1373,16 +1376,8 @@
             .filter(ChangesetStatus.repo == self)\
             .filter(ChangesetStatus.version == 0)\
             .filter(ChangesetStatus.revision.in_(revisions))
+
         grouped = {}
-
-        stat = ChangesetStatus.DEFAULT
-        status_lbl = ChangesetStatus.get_status_lbl(stat)
-        for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
-            for rev in pr.revisions:
-                pr_id = pr.pull_request_id
-                pr_repo = pr.other_repo.repo_name
-                grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
-
         for stat in statuses.all():
             pr_id = pr_repo = None
             if stat.pull_request:
@@ -1434,25 +1429,15 @@
 
     def __get_instance(self):
         repo_full_path = self.repo_full_path
-        try:
-            alias = get_scm(repo_full_path)[0]
-            log.debug('Creating instance of %s repository from %s'
-                      % (alias, repo_full_path))
-            backend = get_backend(alias)
-        except VCSError:
-            log.error(traceback.format_exc())
-            log.error('Perhaps this repository is in db and not in '
-                      'filesystem run rescan repositories with '
-                      '"destroy old data " option from admin panel')
-            return
+
+        alias = get_scm(repo_full_path)[0]
+        log.debug('Creating instance of %s repository from %s'
+                  % (alias, repo_full_path))
+        backend = get_backend(alias)
 
         if alias == 'hg':
-
             repo = backend(safe_str(repo_full_path), create=False,
                            baseui=self._ui)
-            # skip hidden web repository
-            if repo._get_hidden():
-                return
         else:
             repo = backend(repo_full_path, create=False)
 
@@ -2101,19 +2086,16 @@
         inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
         log.debug('for repo %s got %s invalidation objects'
                   % (safe_str(repo_name), inv_objs))
-        try:
-            for inv_obj in inv_objs:
-                log.debug('marking %s key for invalidation based on repo_name=%s'
-                          % (inv_obj, safe_str(repo_name)))
-                if delete:
-                    Session().delete(inv_obj)
-                else:
-                    inv_obj.cache_active = False
-                    Session().add(inv_obj)
-            Session().commit()
-        except Exception:
-            log.error(traceback.format_exc())
-            Session().rollback()
+
+        for inv_obj in inv_objs:
+            log.debug('marking %s key for invalidation based on repo_name=%s'
+                      % (inv_obj, safe_str(repo_name)))
+            if delete:
+                Session().delete(inv_obj)
+            else:
+                inv_obj.cache_active = False
+                Session().add(inv_obj)
+        Session().commit()
 
     @classmethod
     def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None):
@@ -2129,19 +2111,15 @@
         if valid_cache_keys and cache_key in valid_cache_keys:
             return True
 
-        try:
-            inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
-            if not inv_obj:
-                inv_obj = CacheInvalidation(cache_key, repo_name)
-            was_valid = inv_obj.cache_active
-            inv_obj.cache_active = True
-            Session().add(inv_obj)
-            Session().commit()
-            return was_valid
-        except Exception:
-            log.error(traceback.format_exc())
-            Session().rollback()
-            return False
+        inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
+        if not inv_obj:
+            inv_obj = CacheInvalidation(cache_key, repo_name)
+        if inv_obj.cache_active:
+            return True
+        inv_obj.cache_active = True
+        Session().add(inv_obj)
+        Session().commit()
+        return False
 
     @classmethod
     def get_valid_cache_keys(cls):
@@ -2156,6 +2134,7 @@
     __tablename__ = 'changeset_comments'
     __table_args__ = (
         Index('cc_revision_idx', 'revision'),
+        Index('cc_pull_request_id_idx', 'pull_request_id'),
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
     )
@@ -2173,7 +2152,10 @@
 
     author = relationship('User', lazy='joined')
     repo = relationship('Repository')
-    status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
+    # status_change is frequently used directly in templates - make it a lazy
+    # join to avoid fetching each related ChangesetStatus on demand.
+    # There will only be one ChangesetStatus referencing each comment so the join will not explode.
+    status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='joined')
     pull_request = relationship('PullRequest')
 
     @classmethod
@@ -2199,6 +2181,8 @@
     __table_args__ = (
         Index('cs_revision_idx', 'revision'),
         Index('cs_version_idx', 'version'),
+        Index('cs_pull_request_id_idx', 'pull_request_id'),
+        Index('cs_changeset_comment_id_idx', 'changeset_comment_id'),
         UniqueConstraint('repo_id', 'revision', 'version'),
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
@@ -2248,6 +2232,8 @@
 class PullRequest(Base, BaseModel):
     __tablename__ = 'pull_requests'
     __table_args__ = (
+        Index('pr_org_repo_id_idx', 'org_repo_id'),
+        Index('pr_other_repo_id_idx', 'other_repo_id'),
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
     )
@@ -2275,7 +2261,7 @@
 
     @revisions.setter
     def revisions(self, val):
-        self._revisions = ':'.join(val)
+        self._revisions = safe_unicode(':'.join(val))
 
     @property
     def org_ref_parts(self):
@@ -2287,12 +2273,12 @@
 
     author = relationship('User', lazy='joined')
     reviewers = relationship('PullRequestReviewers',
-                             cascade="all, delete, delete-orphan")
+                             cascade="all, delete-orphan")
     org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
     other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
     statuses = relationship('ChangesetStatus')
     comments = relationship('ChangesetComment',
-                             cascade="all, delete, delete-orphan")
+                             cascade="all, delete-orphan")
 
     def is_closed(self):
         return self.status == self.STATUS_CLOSED
@@ -2364,7 +2350,7 @@
 
     created_by_user = relationship('User')
     notifications_to_users = relationship('UserNotification', lazy='joined',
-                                          cascade="all, delete, delete-orphan")
+                                          cascade="all, delete-orphan")
 
     @property
     def recipients(self):
@@ -2387,8 +2373,10 @@
         for u in recipients:
             assoc = UserNotification()
             assoc.notification = notification
-            u.notifications.append(assoc)
+            assoc.user_id = u.user_id
+            Session().add(assoc)
         Session().add(notification)
+        Session().flush() # assign notificaiton.notification_id
         return notification
 
     @property
@@ -2410,8 +2398,7 @@
     sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
 
     user = relationship('User', lazy="joined")
-    notification = relationship('Notification', lazy="joined",
-                                order_by=lambda: Notification.created_on.desc(),)
+    notification = relationship('Notification', lazy="joined")
 
     def mark_as_read(self):
         self.read = True
--- a/kallithea/model/forms.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/model/forms.py	Wed Jan 07 13:37:28 2015 +0100
@@ -514,11 +514,11 @@
 
         filename = All(v.BasePath()(),
                        v.UnicodeString(strip=True, required=False))
-        description = v.UnicodeString(required=False, if_missing='')
+        description = v.UnicodeString(required=False, if_missing=u'')
         lifetime = v.OneOf(lifetime_options)
         mimetype = v.UnicodeString(required=False, if_missing=None)
         content = v.UnicodeString(required=True, not_empty=True)
-        public = v.UnicodeString(required=False, if_missing='')
-        private = v.UnicodeString(required=False, if_missing='')
+        public = v.UnicodeString(required=False, if_missing=u'')
+        private = v.UnicodeString(required=False, if_missing=u'')
 
     return _GistForm
--- a/kallithea/model/meta.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/model/meta.py	Wed Jan 07 13:37:28 2015 +0100
@@ -25,17 +25,17 @@
 cache_manager = cache.CacheManager()
 
 __all__ = ['Base', 'Session']
+
 #
-# SQLAlchemy session manager. Updated by model.init_model()
+# SQLAlchemy session manager.
 #
-Session = scoped_session(
-                sessionmaker(
-                    query_cls=caching_query.query_callable(cache_manager),
-                    expire_on_commit=True,
-                )
-          )
+session_factory = sessionmaker(
+    query_cls=caching_query.query_callable(cache_manager),
+    expire_on_commit=True)
+Session = scoped_session(session_factory)
 
-# The declarative Base
+# The base class for declarative schemas in db.py
+# Engine is injected when model.__init__.init_model() sets meta.Base.metadata.bind
 Base = declarative_base()
 
 #to use cache use this in query
--- a/kallithea/model/notification.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/model/notification.py	Wed Jan 07 13:37:28 2015 +0100
@@ -31,6 +31,7 @@
 
 from pylons import tmpl_context as c
 from pylons.i18n.translation import _
+from sqlalchemy.orm import joinedload, subqueryload
 
 import kallithea
 from kallithea.lib import helpers as h
@@ -167,7 +168,10 @@
         q = UserNotification.query()\
             .filter(UserNotification.user == user)\
             .join((Notification, UserNotification.notification_id ==
-                                 Notification.notification_id))
+                                 Notification.notification_id))\
+            .options(joinedload('notification'))\
+            .options(subqueryload('notification.created_by_user'))\
+            .order_by(Notification.created_on.desc())
 
         if filter_:
             q = q.filter(Notification.type_.in_(filter_))
--- a/kallithea/model/repo.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/model/repo.py	Wed Jan 07 13:37:28 2015 +0100
@@ -32,8 +32,9 @@
 import logging
 import traceback
 from datetime import datetime
+from sqlalchemy.orm import subqueryload
+
 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, \
@@ -44,7 +45,7 @@
 from kallithea.model import BaseModel
 from kallithea.model.db import Repository, UserRepoToPerm, UserGroupRepoToPerm, \
     UserRepoGroupToPerm, UserGroupRepoGroupToPerm, User, Permission, \
-    Statistics, UserGroup, Ui, RepoGroup, \
+    Statistics, UserGroup, UserGroupMember, Ui, RepoGroup, \
     Setting, RepositoryField
 
 from kallithea.lib import helpers as h
@@ -146,7 +147,9 @@
 
     def get_user_groups_js(self):
         user_groups = self.sa.query(UserGroup) \
-            .filter(UserGroup.users_group_active == True).all()
+            .filter(UserGroup.users_group_active == True) \
+            .options(subqueryload(UserGroup.members)) \
+            .all()
         user_groups = UserGroupList(user_groups, perm_set=['usergroup.read',
                                                            'usergroup.write',
                                                            'usergroup.admin'])
@@ -238,7 +241,7 @@
                 "last_changeset": last_rev(repo.repo_name, cs_cache),
                 "last_rev_raw": cs_cache.get('revision'),
                 "desc": desc(repo.description),
-                "owner": h.person(repo.user.username),
+                "owner": h.person(repo.user),
                 "state": state(repo.repo_state),
                 "rss": rss_lnk(repo.repo_name),
                 "atom": atom_lnk(repo.repo_name),
@@ -248,7 +251,7 @@
                 row.update({
                     "action": repo_actions(repo.repo_name),
                     "owner": owner_actions(repo.user.user_id,
-                                           h.person(repo.user.username))
+                                           h.person(repo.user))
                 })
             repos_data.append(row)
 
--- a/kallithea/model/user.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/model/user.py	Wed Jan 07 13:37:28 2015 +0100
@@ -91,23 +91,20 @@
         # raises UserCreationError if it's not allowed
         check_allowed_create_user(user_data, cur_user)
         from kallithea.lib.auth import get_crypt_password
-        try:
-            new_user = User()
-            for k, v in form_data.items():
-                if k == 'password':
-                    v = get_crypt_password(v)
-                if k == 'firstname':
-                    k = 'name'
-                setattr(new_user, k, v)
 
-            new_user.api_key = generate_api_key(form_data['username'])
-            self.sa.add(new_user)
+        new_user = User()
+        for k, v in form_data.items():
+            if k == 'password':
+                v = get_crypt_password(v)
+            if k == 'firstname':
+                k = 'name'
+            setattr(new_user, k, v)
 
-            log_create_user(new_user.get_dict(), cur_user)
-            return new_user
-        except Exception:
-            log.error(traceback.format_exc())
-            raise
+        new_user.api_key = generate_api_key(form_data['username'])
+        self.sa.add(new_user)
+
+        log_create_user(new_user.get_dict(), cur_user)
+        return new_user
 
     def create_or_update(self, username, password, email, firstname='',
                          lastname='', active=True, admin=False,
@@ -185,104 +182,104 @@
         from kallithea.model.notification import NotificationModel
         import kallithea.lib.helpers as h
 
-        try:
-            form_data['admin'] = False
-            form_data['extern_name'] = EXTERN_TYPE_INTERNAL
-            form_data['extern_type'] = EXTERN_TYPE_INTERNAL
-            new_user = self.create(form_data)
+        form_data['admin'] = False
+        form_data['extern_name'] = EXTERN_TYPE_INTERNAL
+        form_data['extern_type'] = EXTERN_TYPE_INTERNAL
+        new_user = self.create(form_data)
 
-            self.sa.add(new_user)
-            self.sa.flush()
+        self.sa.add(new_user)
+        self.sa.flush()
 
-            # notification to admins
-            subject = _('New user registration')
-            body = ('New user registration\n'
-                    '---------------------\n'
-                    '- Username: %s\n'
-                    '- Full Name: %s\n'
-                    '- Email: %s\n')
-            body = body % (new_user.username, new_user.full_name, new_user.email)
-            edit_url = h.canonical_url('edit_user', id=new_user.user_id)
-            email_kwargs = {'registered_user_url': edit_url, 'new_username': new_user.username}
-            NotificationModel().create(created_by=new_user, subject=subject,
-                                       body=body, recipients=None,
-                                       type_=Notification.TYPE_REGISTRATION,
-                                       email_kwargs=email_kwargs)
-        except Exception:
-            log.error(traceback.format_exc())
-            raise
+        # notification to admins
+        subject = _('New user registration')
+        body = ('New user registration\n'
+                '---------------------\n'
+                '- Username: %s\n'
+                '- Full Name: %s\n'
+                '- Email: %s\n')
+        body = body % (new_user.username, new_user.full_name, new_user.email)
+        edit_url = h.canonical_url('edit_user', id=new_user.user_id)
+        email_kwargs = {'registered_user_url': edit_url, 'new_username': new_user.username}
+        NotificationModel().create(created_by=new_user, subject=subject,
+                                   body=body, recipients=None,
+                                   type_=Notification.TYPE_REGISTRATION,
+                                   email_kwargs=email_kwargs)
 
     def update(self, user_id, form_data, skip_attrs=[]):
         from kallithea.lib.auth import get_crypt_password
-        try:
-            user = self.get(user_id, cache=False)
-            if user.username == User.DEFAULT_USER:
-                raise DefaultUserException(
-                                _("You can't Edit this user since it's "
-                                  "crucial for entire application"))
+
+        user = self.get(user_id, cache=False)
+        if user.username == User.DEFAULT_USER:
+            raise DefaultUserException(
+                            _("You can't Edit this user since it's "
+                              "crucial for entire application"))
 
-            for k, v in form_data.items():
-                if k in skip_attrs:
-                    continue
-                if k == 'new_password' and v:
-                    user.password = get_crypt_password(v)
-                else:
-                    # old legacy thing orm models store firstname as name,
-                    # need proper refactor to username
-                    if k == 'firstname':
-                        k = 'name'
-                    setattr(user, k, v)
-            self.sa.add(user)
-        except Exception:
-            log.error(traceback.format_exc())
-            raise
+        for k, v in form_data.items():
+            if k in skip_attrs:
+                continue
+            if k == 'new_password' and v:
+                user.password = get_crypt_password(v)
+            else:
+                # old legacy thing orm models store firstname as name,
+                # need proper refactor to username
+                if k == 'firstname':
+                    k = 'name'
+                setattr(user, k, v)
+        self.sa.add(user)
 
     def update_user(self, user, **kwargs):
         from kallithea.lib.auth import get_crypt_password
-        try:
-            user = self._get_user(user)
-            if user.username == User.DEFAULT_USER:
-                raise DefaultUserException(
-                    _("You can't Edit this user since it's"
-                      " crucial for entire application")
-                )
+
+        user = self._get_user(user)
+        if user.username == User.DEFAULT_USER:
+            raise DefaultUserException(
+                _("You can't Edit this user since it's"
+                  " crucial for entire application")
+            )
 
-            for k, v in kwargs.items():
-                if k == 'password' and v:
-                    v = get_crypt_password(v)
+        for k, v in kwargs.items():
+            if k == 'password' and v:
+                v = get_crypt_password(v)
 
-                setattr(user, k, v)
-            self.sa.add(user)
-            return user
-        except Exception:
-            log.error(traceback.format_exc())
-            raise
+            setattr(user, k, v)
+        self.sa.add(user)
+        return user
 
     def delete(self, user, cur_user=None):
         if not cur_user:
             cur_user = getattr(get_current_authuser(), 'username', None)
         user = self._get_user(user)
 
-        try:
-            if user.username == User.DEFAULT_USER:
-                raise DefaultUserException(
-                    _(u"You can't remove this user since it's"
-                      " crucial for entire application")
-                )
-            if user.repositories:
-                repos = [x.repo_name for x in user.repositories]
-                raise UserOwnsReposException(
-                    _(u'user "%s" still owns %s repositories and cannot be '
-                      'removed. Switch owners or remove those repositories. %s')
-                    % (user.username, len(repos), ', '.join(repos))
-                )
-            self.sa.delete(user)
+        if user.username == User.DEFAULT_USER:
+            raise DefaultUserException(
+                _(u"You can't remove this user since it's"
+                  " crucial for entire application")
+            )
+        if user.repositories:
+            repos = [x.repo_name for x in user.repositories]
+            raise UserOwnsReposException(
+                _(u'User "%s" still owns %s repositories and cannot be '
+                  'removed. Switch owners or remove those repositories: %s')
+                % (user.username, len(repos), ', '.join(repos))
+            )
+        if user.repo_groups:
+            repogroups = [x.group_name for x in user.repo_groups]
+            raise UserOwnsReposException(
+                _(u'User "%s" still owns %s repository groups and cannot be '
+                  'removed. Switch owners or remove those repository groups: %s')
+                % (user.username, len(repogroups), ', '.join(repogroups))
+            )
+        if user.user_groups:
+            usergroups = [x.users_group_name for x in user.user_groups]
+            raise UserOwnsReposException(
+                _(u'User "%s" still owns %s user groups and cannot be '
+                  'removed. Switch owners or remove those user groups: %s')
+                % (user.username, len(usergroups), ', '.join(usergroups))
+            )
+        self.sa.delete(user)
 
-            from kallithea.lib.hooks import log_delete_user
-            log_delete_user(user.get_dict(), cur_user)
-        except Exception:
-            log.error(traceback.format_exc())
-            raise
+        from kallithea.lib.hooks import log_delete_user
+        log_delete_user(user.get_dict(), cur_user)
 
     def reset_password_link(self, data):
         from kallithea.lib.celerylib import tasks, run_task
@@ -290,29 +287,25 @@
         import kallithea.lib.helpers as h
 
         user_email = data['email']
-        try:
-            user = User.get_by_email(user_email)
-            if user:
-                log.debug('password reset user found %s' % user)
-                link = h.canonical_url('reset_password_confirmation', key=user.api_key)
-                reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
-                body = EmailNotificationModel().get_email_tmpl(reg_type,
-                                                               'txt',
-                                                               user=user.short_contact,
-                                                               reset_url=link)
-                html_body = EmailNotificationModel().get_email_tmpl(reg_type,
-                                                               'html',
-                                                               user=user.short_contact,
-                                                               reset_url=link)
-                log.debug('sending email')
-                run_task(tasks.send_email, [user_email],
-                         _("Password reset link"), body, html_body)
-                log.info('send new password mail to %s' % user_email)
-            else:
-                log.debug("password reset email %s not found" % user_email)
-        except Exception:
-            log.error(traceback.format_exc())
-            return False
+        user = User.get_by_email(user_email)
+        if user:
+            log.debug('password reset user found %s' % user)
+            link = h.canonical_url('reset_password_confirmation', key=user.api_key)
+            reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
+            body = EmailNotificationModel().get_email_tmpl(reg_type,
+                                                           'txt',
+                                                           user=user.short_contact,
+                                                           reset_url=link)
+            html_body = EmailNotificationModel().get_email_tmpl(reg_type,
+                                                           'html',
+                                                           user=user.short_contact,
+                                                           reset_url=link)
+            log.debug('sending email')
+            run_task(tasks.send_email, [user_email],
+                     _("Password reset link"), body, html_body)
+            log.info('send new password mail to %s' % user_email)
+        else:
+            log.debug("password reset email %s not found" % user_email)
 
         return True
 
@@ -320,32 +313,21 @@
         from kallithea.lib.celerylib import tasks, run_task
         from kallithea.lib import auth
         user_email = data['email']
-        pre_db = True
-        try:
-            user = User.get_by_email(user_email)
-            new_passwd = auth.PasswordGenerator().gen_password(8,
-                            auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
-            if user:
-                user.password = auth.get_crypt_password(new_passwd)
-                Session().add(user)
-                Session().commit()
-                log.info('change password for %s' % user_email)
-            if new_passwd is None:
-                raise Exception('unable to generate new password')
+        user = User.get_by_email(user_email)
+        new_passwd = auth.PasswordGenerator().gen_password(8,
+                        auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
+        if user:
+            user.password = auth.get_crypt_password(new_passwd)
+            Session().add(user)
+            Session().commit()
+            log.info('change password for %s' % user_email)
+        if new_passwd is None:
+            raise Exception('unable to generate new password')
 
-            pre_db = False
-            run_task(tasks.send_email, [user_email],
-                     _('Your new password'),
-                     _('Your new Kallithea password:%s') % (new_passwd,))
-            log.info('send new password mail to %s' % user_email)
-
-        except Exception:
-            log.error('Failed to update user password')
-            log.error(traceback.format_exc())
-            if pre_db:
-                # we rollback only if local db stuff fails. If it goes into
-                # run_task, we're pass rollback state this wouldn't work then
-                Session().rollback()
+        run_task(tasks.send_email, [user_email],
+                 _('Your new password'),
+                 _('Your new Kallithea password:%s') % (new_passwd,))
+        log.info('send new password mail to %s' % user_email)
 
         return True
 
@@ -364,29 +346,21 @@
         if user_id is None and api_key is None and username is None:
             raise Exception('You need to pass user_id, api_key or username')
 
-        try:
-            dbuser = None
-            if user_id:
-                dbuser = self.get(user_id)
-            elif api_key:
-                dbuser = self.get_by_api_key(api_key)
-            elif username:
-                dbuser = self.get_by_username(username)
+        dbuser = None
+        if user_id:
+            dbuser = self.get(user_id)
+        elif api_key:
+            dbuser = self.get_by_api_key(api_key)
+        elif username:
+            dbuser = self.get_by_username(username)
 
-            if dbuser is not None and dbuser.active:
-                log.debug('filling %s data' % dbuser)
-                for k, v in dbuser.get_dict().iteritems():
-                    if k not in ['api_keys', 'permissions']:
-                        setattr(auth_user, k, v)
-            else:
-                return False
-
-        except Exception:
-            log.error(traceback.format_exc())
-            auth_user.is_authenticated = False
-            return False
-
-        return True
+        if dbuser is not None and dbuser.active:
+            log.debug('filling %s data' % dbuser)
+            for k, v in dbuser.get_dict().iteritems():
+                if k not in ['api_keys', 'permissions']:
+                    setattr(auth_user, k, v)
+            return True
+        return False
 
     def has_perm(self, user, perm):
         perm = self._get_perm(perm)
--- a/kallithea/model/validators.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/model/validators.py	Wed Jan 07 13:37:28 2015 +0100
@@ -22,6 +22,7 @@
 from collections import defaultdict
 from pylons.i18n.translation import _
 from webhelpers.pylonslib.secure_form import authentication_token
+import sqlalchemy
 
 from formencode.validators import (
     UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set,
@@ -87,7 +88,7 @@
             value = aslist(value, ',')
             seen = set()
             return [c for c in value if not (c in seen or seen.add(c))]
-    
+
         def empty_value(self, value):
             return []
 
@@ -142,7 +143,7 @@
             try:
                 User.query().filter(User.active == True)\
                     .filter(User.username == value).one()
-            except Exception:
+            except sqlalchemy.exc.InvalidRequestError: # NoResultFound/MultipleResultsFound
                 msg = M(self, 'invalid_username', state, username=value)
                 raise formencode.Invalid(msg, value, state,
                     error_dict=dict(username=msg)
--- a/kallithea/public/css/style.css	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/public/css/style.css	Wed Jan 07 13:37:28 2015 +0100
@@ -4856,6 +4856,12 @@
     display: inline-block;
     width: 0;
 }
+table.code-highlighttable div.code-highlight pre u.cr:before,
+table.code-difftable td.code pre u.cr:before {
+    content: "\21a4";
+    display: inline-block;
+    color: rgba(0,0,0,0.5);
+}
 table.code-highlighttable div.code-highlight pre u,
 table.code-difftable td.code pre u {
     color: rgba(0,0,0,0.15);
@@ -4864,7 +4870,7 @@
 table.code-difftable td.code pre i {
     border-style: solid;
     border-left-width: 1px;
-    color: rgba(0,0,0,0.15);
+    color: rgba(0,0,0,0.5);
 }
 
 /** LINE NUMBERS **/
--- a/kallithea/public/js/base.js	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/public/js/base.js	Wed Jan 07 13:37:28 2015 +0100
@@ -669,16 +669,16 @@
 
         if(lineno === undefined){
             alert('Error submitting, line ' + lineno + ' not found.');
-            return
+            return;
         }
         if(f_path === undefined){
             alert('Error submitting, file path ' + f_path + ' not found.');
-            return
+            return;
         }
 
         var text = $('#text_'+lineno).val();
         if(text == ""){
-            return
+            return;
         }
 
         $overlay.show();
@@ -1471,7 +1471,7 @@
         '         <input type="hidden" value="{2}" name="review_members" />\n'+
         '         <div class="reviewer_member_remove action_button" onclick="removeReviewMember({2})">\n'+
         '             <i class="icon-minus-circled" style="color: #FF4444;"></i>\n'+
-        '         </div>\n'+
+        '         </div> (add not saved)\n'+
         '       </div>\n'+
         '     </li>\n'
         ).format(gravatar_link, displayname, id);
@@ -1490,7 +1490,7 @@
     var $li = $('#reviewer_{0}'.format(reviewer_id));
     $li.find('div div').css("text-decoration", "line-through");
     $li.find('input').remove();
-    $li.find('.reviewer_member_remove').remove();
+    $li.find('.reviewer_member_remove').replaceWith('&nbsp;(remove not saved)');
 }
 
 /* handle "Save Changes" of addReviewMember and removeReviewMember on PR */
@@ -1778,12 +1778,6 @@
     $('#remove_element').click(function(e){
             $availableselect.append($selectedselect.children('option:selected'));
         });
-    $('#add_all_elements').click(function(e){
-            $selectedselect.append($availableselect.children('option'));
-        });
-    $('#remove_all_elements').click(function(e){
-            $availableselect.append($selectedselect.children('option'));
-        });
 
     $('#'+form_id).submit(function(){
             $selectedselect.children('option').each(function(i, e){
--- a/kallithea/public/js/pyroutes_map.js	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/public/js/pyroutes_map.js	Wed Jan 07 13:37:28 2015 +0100
@@ -1,10 +1,10 @@
 //Format is key == name
 //    "mark_error_fixed": [ # key
-//        "/mark_error_fixed/%(error_id)s", #url template 
-//        [ 
+//        "/mark_error_fixed/%(error_id)s", #url template
+//        [
 //            "error_id" # list of args
 //        ]
-//    ], 
+//    ],
 //
 var PROUTES_MAP = {
 
--- a/kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html	Wed Jan 07 13:37:28 2015 +0100
@@ -7,7 +7,7 @@
     (_('Total repositories'), c.repo_group.repositories_recursive_count, ''),
     (_('Children groups'), c.repo_group.children.count(), ''),
     (_('Created on'), h.fmt_date(c.repo_group.created_on), ''),
-    (_('Owner'), h.person(c.repo_group.user.username), '')
+    (_('Owner'), h.person(c.repo_group.user), '')
  ]
 %>
 %for dt, dd, tt in elems:
--- a/kallithea/templates/admin/user_groups/user_group_edit_advanced.html	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/templates/admin/user_groups/user_group_edit_advanced.html	Wed Jan 07 13:37:28 2015 +0100
@@ -5,7 +5,7 @@
  elems = [
     (_('Members'), len(c.group_members_obj), ''),
     (_('Created on'), h.fmt_date(c.user_group.created_on), ''),
-    (_('Owner'), h.person(c.user_group.user.username), '')
+    (_('Owner'), h.person(c.user_group.user), '')
     ]
 %>
 %for dt, dd, tt in elems:
--- a/kallithea/templates/admin/user_groups/user_group_edit_settings.html	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/templates/admin/user_groups/user_group_edit_settings.html	Wed Jan 07 13:37:28 2015 +0100
@@ -39,10 +39,6 @@
                                             <div style="float:left">
                                                 <div class="text" style="padding: 0px 0px 6px;">${_('Chosen group members')}</div>
                                                 ${h.select('users_group_members',[x[0] for x in c.group_members],c.group_members,multiple=True,size=8,style="min-width:210px")}
-                                               <div id="remove_all_elements" style="cursor:pointer;text-align:center;margin: 4px; ">
-                                                   ${_('Remove all elements')}
-                                                   <i style="cursor:pointer; font-size: 16px;position: relative; bottom: -2px; padding: 2px" class="icon-right-open"></i>
-                                               </div>
                                             </div>
                                             <div style="float:left;width:20px;padding-top:50px">
                                                 <i style="cursor:pointer; padding: 4px; font-size: 16px" id="add_element" class="icon-left-open"></i>
@@ -52,9 +48,6 @@
                                             <div style="float:left">
                                                  <div class="text" style="padding: 0px 0px 6px;">${_('Available members')}</div>
                                                  ${h.select('available_members',[],c.available_members,multiple=True,size=8,style="min-width:210px")}
-                                                 <div id="add_all_elements" style="cursor:pointer;text-align:center;margin: 4px;">
-                                                     <i style="cursor:pointer;font-size: 16px;position: relative; bottom: -2px; padding: 4px" class="icon-left-open"></i>${_('Add all elements')}
-                                                 </div>
                                             </div>
                                         </div>
                                     </td>
--- a/kallithea/templates/base/base.html	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/templates/base/base.html	Wed Jan 07 13:37:28 2015 +0100
@@ -7,7 +7,7 @@
         <div id="logo">
           <a href="${h.url('home')}" style="display: block;">
             <div class="header">
-                <img src="${h.url('/images/kallithea-logo.svg')}" onerror="this.src='${h.url('/images/kallithea-logo.png')}'" alt="Kallithea"/>
+                <img src="${h.url('/images/kallithea-logo.svg')}" onerror="this.onerror='';this.src='${h.url('/images/kallithea-logo.png')}'" alt="Kallithea"/>
             </div>
             %if c.site_name:
              <div class="branding">${c.site_name}</div>
--- a/kallithea/templates/base/root.html	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/templates/base/root.html	Wed Jan 07 13:37:28 2015 +0100
@@ -1,6 +1,5 @@
 ## -*- coding: utf-8 -*-
 <!DOCTYPE html>
-
 <html xmlns="http://www.w3.org/1999/xhtml">
     <head>
         <title>${self.title()}</title>
@@ -9,25 +8,17 @@
         <link rel="icon" href="${h.url('/images/favicon.ico')}" type="image/png" />
 
         ## CSS ###
-        <%def name="css()">
-            <link rel="stylesheet" type="text/css" href="${h.url('/js/select2/select2.css', ver=c.kallithea_version)}"/>
-            <link rel="stylesheet" type="text/css" href="${h.url('/css/pygments.css', ver=c.kallithea_version)}"/>
-            <link rel="stylesheet" type="text/css" href="${h.url('/css/style.css', ver=c.kallithea_version)}" media="screen"/>
-            <link rel="stylesheet" type="text/css" href="${h.url('/css/contextbar.css', ver=c.kallithea_version)}" media="screen"/>
-
-            <link rel="stylesheet" type="text/css" href="${h.url('/fontello/css/kallithea.css', ver=c.kallithea_version)}">
-            ## EXTRA FOR CSS
-            ${self.css_extra()}
-        </%def>
-
+        <link rel="stylesheet" type="text/css" href="${h.url('/js/select2/select2.css', ver=c.kallithea_version)}"/>
+        <link rel="stylesheet" type="text/css" href="${h.url('/css/pygments.css', ver=c.kallithea_version)}"/>
+        <link rel="stylesheet" type="text/css" href="${h.url('/css/style.css', ver=c.kallithea_version)}" media="screen"/>
+        <link rel="stylesheet" type="text/css" href="${h.url('/css/contextbar.css', ver=c.kallithea_version)}" media="screen"/>
+        <link rel="stylesheet" type="text/css" href="${h.url('/fontello/css/kallithea.css', ver=c.kallithea_version)}">
         <%def name="css_extra()"></%def>
-
-        ${self.css()}
+        ${self.css_extra()}
 
         ## JAVASCRIPT ##
-        <%def name="js()">
-            <script type="text/javascript">
-            //JS translations map
+        <script type="text/javascript">
+            ## JS translations map
             var TRANSLATION_MAP = {
                 'Add Another Comment':'${_("Add Another Comment")}',
                 'Stop following this repository':"${_('Stop following this repository')}",
@@ -46,7 +37,7 @@
                 '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} ?')}",
+                'Confirm to revoke permission for {0}: {1} ?': "${_('Confirm to revoke permission for {0}: {1} ?')}",
                 'enabled': "${_('enabled')}",
                 'disabled': "${_('disabled')}",
                 'Select changeset': "${_('Select changeset')}",
@@ -65,24 +56,24 @@
             %if hasattr(c, 'repo_name'):
                 var REPO_NAME = "${c.repo_name}";
             %endif
-            </script>
-            <script type="text/javascript" src="${h.url('/js/yui.2.9.js', ver=c.kallithea_version)}"></script>
-            <script type="text/javascript" src="${h.url('/js/jquery-1.11.1.min.js', ver=c.kallithea_version)}"></script>
-            <script type="text/javascript" src="${h.url('/js/bootstrap.js', ver=c.kallithea_version)}"></script>
-            <script type="text/javascript" src="${h.url('/js/select2/select2.js', ver=c.kallithea_version)}"></script>
-            <script type="text/javascript" src="${h.url('/js/mousetrap.js', ver=c.kallithea_version)}"></script>
-            <!--[if lt IE 9]>
-               <script language="javascript" type="text/javascript" src="${h.url('/js/excanvas.min.js')}"></script>
-            <![endif]-->
-            <script type="text/javascript" src="${h.url('/js/yui.flot.js', ver=c.kallithea_version)}"></script>
-            <script type="text/javascript" src="${h.url('/js/native.history.js', ver=c.kallithea_version)}"></script>
-            <script type="text/javascript" src="${h.url('/js/pyroutes_map.js', ver=c.kallithea_version)}"></script>
-            <script type="text/javascript" src="${h.url('/js/base.js', ver=c.kallithea_version)}"></script>
-           ## EXTRA FOR JS
-           ${self.js_extra()}
-            <script type="text/javascript">
+        </script>
+        <script type="text/javascript" src="${h.url('/js/yui.2.9.js', ver=c.kallithea_version)}"></script>
+        <script type="text/javascript" src="${h.url('/js/jquery-1.11.1.min.js', ver=c.kallithea_version)}"></script>
+        <script type="text/javascript" src="${h.url('/js/bootstrap.js', ver=c.kallithea_version)}"></script>
+        <script type="text/javascript" src="${h.url('/js/select2/select2.js', ver=c.kallithea_version)}"></script>
+        <script type="text/javascript" src="${h.url('/js/mousetrap.js', ver=c.kallithea_version)}"></script>
+        <!--[if lt IE 9]>
+           <script language="javascript" type="text/javascript" src="${h.url('/js/excanvas.min.js')}"></script>
+        <![endif]-->
+        <script type="text/javascript" src="${h.url('/js/yui.flot.js', ver=c.kallithea_version)}"></script>
+        <script type="text/javascript" src="${h.url('/js/native.history.js', ver=c.kallithea_version)}"></script>
+        <script type="text/javascript" src="${h.url('/js/pyroutes_map.js', ver=c.kallithea_version)}"></script>
+        <script type="text/javascript" src="${h.url('/js/base.js', ver=c.kallithea_version)}"></script>
+        ## EXTRA FOR JS
+        <%def name="js_extra()"></%def>
+        ${self.js_extra()}
+        <script type="text/javascript">
             (function(window,undefined){
-                // Prepare
                 var History = window.History; // Note: We are using a capital H instead of a lower h
                 if ( !History.enabled ) {
                      // History.js is disabled for this browser.
@@ -113,16 +104,14 @@
               pyroutes.register('repo_size', "${h.url('repo_size', repo_name='%(repo_name)s')}", ['repo_name']);
               pyroutes.register('changeset_comment_preview', "${h.url('changeset_comment_preview', repo_name='%(repo_name)s')}", ['repo_name']);
               pyroutes.register('repo_refs_data', "${h.url('repo_refs_data', repo_name='%(repo_name)s')}", ['repo_name']);
-           });
-            </script>
-        </%def>
-        <%def name="js_extra()"></%def>
-        ${self.js()}
+             });
+        </script>
+
         <%def name="head_extra()"></%def>
         ${self.head_extra()}
     </head>
     <body id="body">
-     ## IE hacks
+      ## IE hacks
       <!--[if IE 7]>
       <script>$(document.body).addClass('ie7')</script>
       <![endif]-->
--- a/kallithea/templates/changeset/diff_block.html	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/templates/changeset/diff_block.html	Wed Jan 07 13:37:28 2015 +0100
@@ -140,7 +140,7 @@
                 })
             .show();
         });
-    
+
     $('.btn-image-diff-swap').mousedown(function(e){
         $('#'+e.currentTarget.id+'-img-a.img-diff-swapable')
           .before($('#'+e.currentTarget.id+'-img-b.img-diff-swapable'));
--- a/kallithea/templates/errors/error_document.html	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/templates/errors/error_document.html	Wed Jan 07 13:37:28 2015 +0100
@@ -8,9 +8,6 @@
         <link rel="icon" href="${h.url('/images/favicon.ico')}" type="image/png" />
 
         <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
-        %if c.redirect_time:
-            <meta http-equiv="refresh" content="${c.redirect_time}; url=${c.url_redirect}"/>
-        %endif
 
         <!-- stylesheets -->
         <link rel="stylesheet" type="text/css" href="${h.url('/css/style.css')}" media="screen"/>
@@ -43,13 +40,8 @@
 
                     <p>${c.error_explanation}</p>
 
-                    %if c.redirect_time:
-                        <p>${_('You will be redirected to %s in %s seconds') % (c.redirect_module,c.redirect_time)}</p>
-                    %endif
-
                 </div>
             </div>
-            <!-- end login -->
         </div>
     </body>
 
--- a/kallithea/templates/login.html	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/templates/login.html	Wed Jan 07 13:37:28 2015 +0100
@@ -14,7 +14,7 @@
         <div id="logo">
           <a href="${h.url('home')}" style="display: block;">
             <div class="header">
-                <img src="${h.url('/images/kallithea-logo.svg')}" onerror="this.src='${h.url('/images/kallithea-logo.png')}'" alt="Kallithea"/>
+                <img src="${h.url('/images/kallithea-logo.svg')}" onerror="this.onerror='';this.src='${h.url('/images/kallithea-logo.png')}'" alt="Kallithea"/>
             </div>
             %if c.site_name:
              <div class="branding">${c.site_name}</div>
--- a/kallithea/templates/password_reset.html	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/templates/password_reset.html	Wed Jan 07 13:37:28 2015 +0100
@@ -14,7 +14,7 @@
         <div id="logo">
           <a href="${h.url('home')}" style="display: block;">
             <div class="header">
-                <img src="${h.url('/images/kallithea-logo.svg')}" onerror="this.src='${h.url('/images/kallithea-logo.png')}'" alt="Kallithea"/>
+                <img src="${h.url('/images/kallithea-logo.svg')}" onerror="this.onerror='';this.src='${h.url('/images/kallithea-logo.png')}'" alt="Kallithea"/>
             </div>
             %if c.site_name:
              <div class="branding">${c.site_name}</div>
--- a/kallithea/templates/register.html	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/templates/register.html	Wed Jan 07 13:37:28 2015 +0100
@@ -12,7 +12,7 @@
         <div id="logo">
           <a href="${h.url('home')}" style="display: block;">
             <div class="header">
-                <img src="${h.url('/images/kallithea-logo.svg')}" onerror="this.src='${h.url('/images/kallithea-logo.png')}'" alt="Kallithea"/>
+                <img src="${h.url('/images/kallithea-logo.svg')}" onerror="this.onerror='';this.src='${h.url('/images/kallithea-logo.png')}'" alt="Kallithea"/>
             </div>
             %if c.site_name:
              <div class="branding">${c.site_name}</div>
--- a/kallithea/tests/__init__.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/tests/__init__.py	Wed Jan 07 13:37:28 2015 +0100
@@ -157,6 +157,12 @@
     return get_normalized_path(path)
 
 
+import logging
+
+class NullHandler(logging.Handler):
+    def emit(self, record):
+        pass
+
 def init_stack(config=None):
     if not config:
         config = pylons.test.pylonsapp.config
@@ -167,6 +173,8 @@
     # Initialize a translator for tests that utilize i18n
     translator = _get_translator(pylons.config.get('lang'))
     pylons.translator._push_object(translator)
+    h = NullHandler()
+    logging.getLogger("kallithea").addHandler(h)
 
 
 class BaseTestCase(unittest.TestCase):
--- a/kallithea/tests/fixture.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/tests/fixture.py	Wed Jan 07 13:37:28 2015 +0100
@@ -228,7 +228,7 @@
 
     def create_gist(self, **kwargs):
         form_data = {
-            'description': 'new-gist',
+            'description': u'new-gist',
             'owner': TEST_USER_ADMIN_LOGIN,
             'gist_type': GistModel.cls.GIST_PUBLIC,
             'lifetime': -1,
--- a/kallithea/tests/functional/test_admin_auth_settings.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/tests/functional/test_admin_auth_settings.py	Wed Jan 07 13:37:28 2015 +0100
@@ -23,7 +23,7 @@
 
     def test_ldap_save_settings(self):
         self.log_user()
-        if ldap_lib_installed:
+        if not ldap_lib_installed:
             raise SkipTest('skipping due to missing ldap lib')
 
         params = self._enable_plugins('kallithea.lib.auth_modules.auth_internal,kallithea.lib.auth_modules.auth_ldap')
@@ -53,7 +53,7 @@
 
     def test_ldap_error_form_wrong_port_number(self):
         self.log_user()
-        if ldap_lib_installed:
+        if not ldap_lib_installed:
             raise SkipTest('skipping due to missing ldap lib')
 
         params = self._enable_plugins('kallithea.lib.auth_modules.auth_internal,kallithea.lib.auth_modules.auth_ldap')
@@ -80,7 +80,7 @@
 
     def test_ldap_error_form(self):
         self.log_user()
-        if ldap_lib_installed:
+        if not ldap_lib_installed:
             raise SkipTest('skipping due to missing ldap lib')
 
         params = self._enable_plugins('kallithea.lib.auth_modules.auth_internal,kallithea.lib.auth_modules.auth_ldap')
--- a/kallithea/tests/functional/test_admin_gists.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/tests/functional/test_admin_gists.py	Wed Jan 07 13:37:28 2015 +0100
@@ -5,7 +5,7 @@
 
 
 def _create_gist(f_name, content='some gist', lifetime=-1,
-                 description='gist-desc', gist_type='public',
+                 description=u'gist-desc', gist_type='public',
                  owner=TEST_USER_ADMIN_LOGIN):
     gist_mapping = {
         f_name: {'content': content}
@@ -33,7 +33,7 @@
 
         g1 = _create_gist('gist1').gist_access_id
         g2 = _create_gist('gist2', lifetime=1400).gist_access_id
-        g3 = _create_gist('gist3', description='gist3-desc').gist_access_id
+        g3 = _create_gist('gist3', description=u'gist3-desc').gist_access_id
         g4 = _create_gist('gist4', gist_type='private').gist_access_id
         response = self.app.get(url('gists'))
         # Test response...
--- a/kallithea/tests/functional/test_admin_repos.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/tests/functional/test_admin_repos.py	Wed Jan 07 13:37:28 2015 +0100
@@ -79,7 +79,7 @@
         # test if the repository was created on filesystem
         try:
             vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
-        except Exception:
+        except vcs.exceptions.VCSError:
             self.fail('no repo %s in filesystem' % repo_name)
 
         RepoModel().delete(repo_name)
@@ -118,7 +118,7 @@
         # test if the repository was created on filesystem
         try:
             vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
-        except Exception:
+        except vcs.exceptions.VCSError:
             self.fail('no repo %s in filesystem' % repo_name)
 
     def test_create_in_group(self):
@@ -166,7 +166,7 @@
         # test if the repository was created on filesystem
         try:
             vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name_full))
-        except Exception:
+        except vcs.exceptions.VCSError:
             RepoGroupModel().delete(group_name)
             Session().commit()
             self.fail('no repo %s in filesystem' % repo_name)
@@ -254,7 +254,7 @@
         # test if the repository was created on filesystem
         try:
             vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name_full))
-        except Exception:
+        except vcs.exceptions.VCSError:
             RepoGroupModel().delete(group_name)
             Session().commit()
             self.fail('no repo %s in filesystem' % repo_name)
@@ -310,7 +310,7 @@
         # test if the repository was created on filesystem
         try:
             vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name_full))
-        except Exception:
+        except vcs.exceptions.VCSError:
             RepoGroupModel().delete(group_name)
             Session().commit()
             self.fail('no repo %s in filesystem' % repo_name)
@@ -384,7 +384,7 @@
         # test if the repository was created on filesystem
         try:
             vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
-        except Exception:
+        except vcs.exceptions.VCSError:
             self.fail('no repo %s in filesystem' % repo_name)
 
         response = self.app.delete(url('repo', repo_name=repo_name))
@@ -435,7 +435,7 @@
         # test if the repository was created on filesystem
         try:
             vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
-        except Exception:
+        except vcs.exceptions.VCSError:
             self.fail('no repo %s in filesystem' % repo_name)
 
         response = self.app.delete(url('repo', repo_name=repo_name))
--- a/kallithea/tests/functional/test_admin_user_groups.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/tests/functional/test_admin_user_groups.py	Wed Jan 07 13:37:28 2015 +0100
@@ -23,7 +23,9 @@
         response.follow()
 
         self.checkSessionFlash(response,
-                               'Created user group %s' % TEST_USER_GROUP)
+                               'Created user group <a href="/_admin/user_groups/')
+        self.checkSessionFlash(response,
+                               '/edit">%s</a>' % TEST_USER_GROUP)
 
     def test_new(self):
         response = self.app.get(url('new_users_group'))
@@ -45,7 +47,7 @@
         response.follow()
 
         self.checkSessionFlash(response,
-                               'Created user group %s' % users_group_name)
+                               'Created user group ')
 
         gr = Session().query(UserGroup)\
             .filter(UserGroup.users_group_name == users_group_name).one()
@@ -68,7 +70,7 @@
 
         ug = UserGroup.get_by_group_name(users_group_name)
         self.checkSessionFlash(response,
-                               'Created user group %s' % users_group_name)
+                               'Created user group ')
         ## ENABLE REPO CREATE ON A GROUP
         response = self.app.put(url('edit_user_group_default_perms',
                                     id=ug.users_group_id),
@@ -138,7 +140,7 @@
 
         ug = UserGroup.get_by_group_name(users_group_name)
         self.checkSessionFlash(response,
-                               'Created user group %s' % users_group_name)
+                               'Created user group ')
         ## ENABLE REPO CREATE ON A GROUP
         response = self.app.put(url('edit_user_group_default_perms',
                                     id=ug.users_group_id),
--- a/kallithea/tests/functional/test_admin_users.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/tests/functional/test_admin_users.py	Wed Jan 07 13:37:28 2015 +0100
@@ -60,7 +60,8 @@
              'extern_type': 'internal',
              'email': email})
 
-        self.checkSessionFlash(response, '''Created user %s''' % (username))
+        self.checkSessionFlash(response, '''Created user <a href="/_admin/users/''')
+        self.checkSessionFlash(response, '''/edit">%s</a>''' % (username))
 
         new_user = Session().query(User).\
             filter(User.username == username).one()
@@ -166,6 +167,79 @@
 
         self.checkSessionFlash(response, 'Successfully deleted user')
 
+    def test_delete_repo_err(self):
+        self.log_user()
+        username = 'repoerr'
+        reponame = 'repoerr_fail'
+
+        fixture.create_user(name=username)
+        fixture.create_repo(name=reponame, cur_user=username)
+
+        new_user = Session().query(User)\
+            .filter(User.username == username).one()
+        response = self.app.delete(url('user', id=new_user.user_id))
+        self.checkSessionFlash(response, 'User "%s" still '
+                               'owns 1 repositories and cannot be removed. '
+                               'Switch owners or remove those repositories: '
+                               '%s' % (username, reponame))
+
+        response = self.app.delete(url('repo', repo_name=reponame))
+        self.checkSessionFlash(response, 'Deleted repository %s' % reponame)
+
+        response = self.app.delete(url('user', id=new_user.user_id))
+        self.checkSessionFlash(response, 'Successfully deleted user')
+
+    def test_delete_repo_group_err(self):
+        self.log_user()
+        username = 'repogrouperr'
+        groupname = 'repogroup_fail'
+
+        fixture.create_user(name=username)
+        fixture.create_repo_group(name=groupname, cur_user=username)
+
+        new_user = Session().query(User)\
+            .filter(User.username == username).one()
+        response = self.app.delete(url('user', id=new_user.user_id))
+        self.checkSessionFlash(response, 'User "%s" still '
+                               'owns 1 repository groups and cannot be removed. '
+                               'Switch owners or remove those repository groups: '
+                               '%s' % (username, groupname))
+
+        # Relevant _if_ the user deletion succeeded to make sure we can render groups without owner
+        # rg = RepoGroup.get_by_group_name(group_name=groupname)
+        # response = self.app.get(url('repos_groups', id=rg.group_id))
+
+        response = self.app.delete(url('delete_repo_group', group_name=groupname))
+        self.checkSessionFlash(response, 'Removed repository group %s' % groupname)
+
+        response = self.app.delete(url('user', id=new_user.user_id))
+        self.checkSessionFlash(response, 'Successfully deleted user')
+
+    def test_delete_user_group_err(self):
+        self.log_user()
+        username = 'usergrouperr'
+        groupname = 'usergroup_fail'
+
+        fixture.create_user(name=username)
+        ug = fixture.create_user_group(name=groupname, cur_user=username)
+
+        new_user = Session().query(User)\
+            .filter(User.username == username).one()
+        response = self.app.delete(url('user', id=new_user.user_id))
+        self.checkSessionFlash(response, 'User "%s" still '
+                               'owns 1 user groups and cannot be removed. '
+                               'Switch owners or remove those user groups: '
+                               '%s' % (username, groupname))
+
+        # TODO: why do this fail?
+        #response = self.app.delete(url('delete_users_group', id=groupname))
+        #self.checkSessionFlash(response, 'Removed user group %s' % groupname)
+
+        fixture.destroy_user_group(ug.users_group_id)
+
+        response = self.app.delete(url('user', id=new_user.user_id))
+        self.checkSessionFlash(response, 'Successfully deleted user')
+
     def test_show(self):
         response = self.app.get(url('user', id=1))
 
--- a/kallithea/tests/functional/test_login.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/tests/functional/test_login.py	Wed Jan 07 13:37:28 2015 +0100
@@ -347,7 +347,7 @@
             self.assertEqual(['ChangesetController:changeset_raw'],
                              whitelist['api_access_controllers_whitelist'])
 
-            new_api_key = ApiKeyModel().create(TEST_USER_ADMIN_LOGIN, 'test')
+            new_api_key = ApiKeyModel().create(TEST_USER_ADMIN_LOGIN, u'test')
             Session().commit()
             with fixture.anon_access(False):
                 self.app.get(url(controller='changeset',
@@ -361,7 +361,7 @@
             self.assertEqual(['ChangesetController:changeset_raw'],
                              whitelist['api_access_controllers_whitelist'])
 
-            new_api_key = ApiKeyModel().create(TEST_USER_ADMIN_LOGIN, 'test')
+            new_api_key = ApiKeyModel().create(TEST_USER_ADMIN_LOGIN, u'test')
             Session().commit()
             #patch the api key and make it expired
             new_api_key.expires = 0
--- a/kallithea/tests/other/test_libs.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/tests/other/test_libs.py	Wed Jan 07 13:37:28 2015 +0100
@@ -126,7 +126,12 @@
         (dict(hours= -24 * 5), u'5 days ago'),
         (dict(months= -1), u'1 month ago'),
         (dict(months= -1, days= -2), u'1 month and 2 days ago'),
+        (dict(months= -1, days= -20), u'1 month and 19 days ago'),
         (dict(years= -1, months= -1), u'1 year and 1 month ago'),
+        (dict(years= -1, months= -10), u'1 year and 10 months ago'),
+        (dict(years= -2, months= -4), u'2 years and 4 months ago'),
+        (dict(years= -2, months= -11), u'2 years and 11 months ago'),
+        (dict(years= -3, months= -2), u'3 years and 2 months ago'),
     ])
     def test_age(self, age_args, expected):
         from kallithea.lib.utils2 import age
@@ -136,7 +141,30 @@
         self.assertEqual(age(n + delt(**age_args), now=n), expected)
 
     @parameterized.expand([
+        (dict(), u'just now'),
+        (dict(seconds= -1), u'1 second ago'),
+        (dict(seconds= -60 * 2), u'2 minutes ago'),
+        (dict(hours= -1), u'1 hour ago'),
+        (dict(hours= -24), u'1 day ago'),
+        (dict(hours= -24 * 5), u'5 days ago'),
+        (dict(months= -1), u'1 month ago'),
+        (dict(months= -1, days= -2), u'1 month ago'),
+        (dict(months= -1, days= -20), u'1 month ago'),
+        (dict(years= -1, months= -1), u'13 months ago'),
+        (dict(years= -1, months= -10), u'22 months ago'),
+        (dict(years= -2, months= -4), u'2 years ago'),
+        (dict(years= -2, months= -11), u'3 years ago'),
+        (dict(years= -3, months= -2), u'3 years ago'),
+        (dict(years= -4, months= -8), u'5 years ago'),
+    ])
+    def test_age_short(self, age_args, expected):
+        from kallithea.lib.utils2 import age
+        from dateutil import relativedelta
+        n = datetime.datetime(year=2012, month=5, day=17)
+        delt = lambda *args, **kwargs: relativedelta.relativedelta(*args, **kwargs)
+        self.assertEqual(age(n + delt(**age_args), show_short_version=True, now=n), expected)
 
+    @parameterized.expand([
         (dict(), u'just now'),
         (dict(seconds=1), u'in 1 second'),
         (dict(seconds=60 * 2), u'in 2 minutes'),
@@ -339,7 +367,6 @@
       ("_21/121", '21'),
       ("/_21/_12", '21'),
       ("_21/rc/foo", '21'),
-
     ])
     def test_get_repo_by_id(self, test, expected):
         from kallithea.lib.utils import _extract_id_from_repo_name
--- a/kallithea/tests/scripts/test_concurency.py	Mon Jan 05 10:58:24 2015 +0100
+++ b/kallithea/tests/scripts/test_concurency.py	Wed Jan 07 13:37:28 2015 +0100
@@ -200,12 +200,12 @@
 
         try:
             METHOD = sys.argv[3]
-        except Exception:
+        except IndexError:
             pass
 
         try:
             backend = sys.argv[4]
-        except Exception:
+        except IndexError:
             backend = 'hg'
 
         if METHOD == 'pull':
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/whitespacecleanup.sh	Wed Jan 07 13:37:28 2015 +0100
@@ -0,0 +1,18 @@
+#!/bin/bash -x
+
+# Enforce some consistency in whitespace - just to avoid spurious whitespaces changes
+
+files=`hg loc '*.py' '*.html' '*.css' '*.rst' '*.txt' '*.js' | egrep -v '/lockfiles.py|LICENSE-MERGELY.html|/codemirror/|/fontello/|(graph|mergely|native.history|select2/select2|yui.flot)\.js$'`
+sed -i "s,`printf '\t'`,    ,g" $files
+sed -i "s,  *$,,g" $files
+
+sed -i 's,\([^ /]\){,\1 {,g' `hg loc '*.css'`
+sed -i 's|^\([^ /].*,\)\([^ ]\)|\1 \2|g' `hg loc '*.css'`
+
+sed -i 's/^\(    [^: ]*\) *: *\([^/]\)/\1: \2/g' kallithea/public/css/{style,contextbar}.css
+sed -i '1s|, |,|g' kallithea/public/css/{style,contextbar}.css
+sed -i 's/^\([^ ,/]\+ [^,]*[^ ,]\) *, *\(.\)/\1,\n\2/g' kallithea/public/css/{style,contextbar}.css
+sed -i 's/^\([^ ,/].*\)   */\1 /g' kallithea/public/css/{style,contextbar}.css
+sed -i 's,^--$,-- ,g' kallithea/templates/email_templates/main.txt
+
+hg diff