Mercurial > kallithea
changeset 5569:ebc4cc82cce2
Merge stable
author | Mads Kiilerich <madski@unity3d.com> |
---|---|
date | Sat, 07 Nov 2015 13:24:44 +0100 |
parents | 21f80c6cdf0c (diff) 737c3704b44a (current diff) |
children | e856e728b916 |
files | kallithea/controllers/files.py kallithea/model/db.py kallithea/public/js/base.js |
diffstat | 63 files changed, 729 insertions(+), 400 deletions(-) [+] |
line wrap: on
line diff
--- a/CONTRIBUTORS Sat Nov 07 13:16:58 2015 +0100 +++ b/CONTRIBUTORS Sat Nov 07 13:24:44 2015 +0100 @@ -22,6 +22,7 @@ Étienne Gilli <etienne.gilli@gmail.com> 2015 Grzegorz Krason <grzegorz.krason@gmail.com> 2015 Jan Heylen <heyleke@gmail.com> 2015 + Jiří Suchan <yed@vanyli.net> 2015 Kazunari Kobayashi <kobanari@nifty.com> 2015 Kevin Bullock <kbullock@ringworld.org> 2015 kobanari <kobanari@nifty.com> 2015
--- a/development.ini Sat Nov 07 13:16:58 2015 +0100 +++ b/development.ini Sat Nov 07 13:24:44 2015 +0100 @@ -163,6 +163,7 @@ #cheaper-step = 1 ## COMMON ## +#host = 127.0.0.1 host = 0.0.0.0 port = 5000 @@ -280,12 +281,6 @@ #issue_server_link_wiki = https://wiki.example.com/{id} #issue_prefix_wiki = WIKI- -## instance-id prefix -## a prefix key for this instance used for cache invalidation when running -## multiple instances of kallithea, make sure it's globally unique for -## all running kallithea instances. Leave empty if you don't use it -instance_id = - ## alternative return HTTP header for failed authentication. Default HTTP ## response is 401 HTTPUnauthorized. Currently Mercurial clients have trouble with ## handling that. Set this variable to 403 to return HTTPForbidden @@ -301,6 +296,18 @@ ## allows to setup custom hooks in settings page allow_custom_hooks_settings = True +## extra extensions for indexing, space separated and without the leading '.'. +# index.extensions = +# gemfile +# lock + +## extra filenames for indexing, space separated +# index.filenames = +# .dockerignore +# .editorconfig +# INSTALL +# CHANGELOG + #################################### ### CELERY CONFIG #### #################################### @@ -560,16 +567,16 @@ class = StreamHandler args = (sys.stderr,) #level = INFO +level = DEBUG #formatter = generic -level = DEBUG formatter = color_formatter [handler_console_sql] class = StreamHandler args = (sys.stderr,) #level = WARN +level = DEBUG #formatter = generic -level = DEBUG formatter = color_formatter_sql ################
--- a/docs/setup.rst Sat Nov 07 13:16:58 2015 +0100 +++ b/docs/setup.rst Sat Nov 07 13:24:44 2015 +0100 @@ -746,11 +746,6 @@ When running apache as root, please make sure it doesn't run Kallithea as root, for examply by adding: ``user=www-data group=www-data`` to the configuration. -.. note:: - If running Kallithea in multiprocess mode, - make sure you set ``instance_id = *`` in the configuration so each process - gets it's own cache invalidation key. - Example WSGI dispatch script: .. code-block:: python
--- a/docs/usage/performance.rst Sat Nov 07 13:16:58 2015 +0100 +++ b/docs/usage/performance.rst Sat Nov 07 13:24:44 2015 +0100 @@ -38,7 +38,6 @@ scaled horizontally on one (recommended) or multiple machines. In order to scale horizontally you need to do the following: - - Each instance needs its own .ini file and unique ``instance_id`` set. - Each instance's ``data`` storage needs to be configured to be stored on a shared disk storage, preferably together with repositories. This ``data`` dir contains template caches, sessions, whoosh index and is used for
--- a/kallithea/bin/template.ini.mako Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/bin/template.ini.mako Sat Nov 07 13:24:44 2015 +0100 @@ -278,12 +278,6 @@ #issue_server_link_wiki = https://wiki.example.com/{id} #issue_prefix_wiki = WIKI- -<%text>## instance-id prefix</%text> -<%text>## a prefix key for this instance used for cache invalidation when running</%text> -<%text>## multiple instances of kallithea, make sure it's globally unique for</%text> -<%text>## all running kallithea instances. Leave empty if you don't use it</%text> -instance_id = - <%text>## alternative return HTTP header for failed authentication. Default HTTP</%text> <%text>## response is 401 HTTPUnauthorized. Currently Mercurial clients have trouble with</%text> <%text>## handling that. Set this variable to 403 to return HTTPForbidden</%text> @@ -299,6 +293,18 @@ <%text>## allows to setup custom hooks in settings page</%text> allow_custom_hooks_settings = True +<%text>## extra extensions for indexing, space separated and without the leading '.'.</%text> +# index.extensions = +# gemfile +# lock + +<%text>## extra filenames for indexing, space separated</%text> +# index.filenames = +# .dockerignore +# .editorconfig +# INSTALL +# CHANGELOG + <%text>####################################</%text> <%text>### CELERY CONFIG ####</%text> <%text>####################################</%text>
--- a/kallithea/config/conf.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/config/conf.py Sat Nov 07 13:24:44 2015 +0100 @@ -25,19 +25,21 @@ :license: GPLv3, see LICENSE.md for more details. """ -from kallithea.lib.utils2 import __get_lem +from kallithea.lib.utils2 import __get_lem, __get_index_filenames # language map is also used by whoosh indexer, which for those specified # extensions will index it's content LANGUAGES_EXTENSIONS_MAP = __get_lem() -#============================================================================== -# WHOOSH INDEX EXTENSIONS -#============================================================================== -# EXTENSIONS WE WANT TO INDEX CONTENT OFF USING WHOOSH +# Whoosh index targets + +# Extensions we want to index content of using whoosh INDEX_EXTENSIONS = LANGUAGES_EXTENSIONS_MAP.keys() +# Filenames we want to index content of using whoosh +INDEX_FILENAMES = __get_index_filenames() + # list of readme files to search in file tree and display in summary # attached weights defines the search order lower is first ALL_READMES = [
--- a/kallithea/config/deployment.ini_tmpl Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/config/deployment.ini_tmpl Sat Nov 07 13:24:44 2015 +0100 @@ -274,12 +274,6 @@ #issue_server_link_wiki = https://wiki.example.com/{id} #issue_prefix_wiki = WIKI- -## instance-id prefix -## a prefix key for this instance used for cache invalidation when running -## multiple instances of kallithea, make sure it's globally unique for -## all running kallithea instances. Leave empty if you don't use it -instance_id = - ## alternative return HTTP header for failed authentication. Default HTTP ## response is 401 HTTPUnauthorized. Currently Mercurial clients have trouble with ## handling that. Set this variable to 403 to return HTTPForbidden @@ -295,6 +289,18 @@ ## allows to setup custom hooks in settings page allow_custom_hooks_settings = True +## extra extensions for indexing, space separated and without the leading '.'. +# index.extensions = +# gemfile +# lock + +## extra filenames for indexing, space separated +# index.filenames = +# .dockerignore +# .editorconfig +# INSTALL +# CHANGELOG + #################################### ### CELERY CONFIG #### ####################################
--- a/kallithea/config/environment.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/config/environment.py Sat Nov 07 13:24:44 2015 +0100 @@ -34,7 +34,7 @@ from kallithea.lib import helpers from kallithea.lib.auth import set_available_permissions from kallithea.lib.utils import repo2db_mapper, make_ui, set_app_settings,\ - load_rcextensions, check_git_version, set_vcs_config + load_rcextensions, check_git_version, set_vcs_config, set_indexer_config from kallithea.lib.utils2 import engine_from_config, str2bool from kallithea.lib.db_manage import DbManage from kallithea.model import init_model @@ -118,7 +118,7 @@ config['base_path'] = repos_path set_app_settings(config) - instance_id = kallithea.CONFIG.get('instance_id') + instance_id = kallithea.CONFIG.get('instance_id', '*') if instance_id == '*': instance_id = '%s-%s' % (platform.uname()[1], os.getpid()) kallithea.CONFIG['instance_id'] = instance_id @@ -130,6 +130,7 @@ # pylons kallithea.CONFIG.update(config) set_vcs_config(kallithea.CONFIG) + set_indexer_config(kallithea.CONFIG) #check git version check_git_version()
--- a/kallithea/controllers/admin/auth_settings.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/controllers/admin/auth_settings.py Sat Nov 07 13:24:44 2015 +0100 @@ -28,8 +28,8 @@ import traceback from pylons import request, tmpl_context as c, url -from pylons.controllers.util import redirect from pylons.i18n.translation import _ +from webob.exc import HTTPFound from kallithea.lib import helpers as h from kallithea.lib.compat import formatted_json @@ -146,4 +146,4 @@ h.flash(_('error occurred during update of auth settings'), category='error') - return redirect(url('auth_home')) + raise HTTPFound(location=url('auth_home'))
--- a/kallithea/controllers/admin/defaults.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/controllers/admin/defaults.py Sat Nov 07 13:24:44 2015 +0100 @@ -31,8 +31,8 @@ from formencode import htmlfill from pylons import request, tmpl_context as c, url -from pylons.controllers.util import redirect from pylons.i18n.translation import _ +from webob.exc import HTTPFound from kallithea.lib import helpers as h from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator @@ -112,7 +112,7 @@ h.flash(_('Error occurred during update of defaults'), category='error') - return redirect(url('defaults')) + raise HTTPFound(location=url('defaults')) def delete(self, id): """DELETE /defaults/id: Delete an existing item"""
--- a/kallithea/controllers/admin/gists.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/controllers/admin/gists.py Sat Nov 07 13:24:44 2015 +0100 @@ -31,8 +31,8 @@ import formencode.htmlfill from pylons import request, response, tmpl_context as c, url -from pylons.controllers.util import redirect from pylons.i18n.translation import _ +from webob.exc import HTTPFound, HTTPNotFound, HTTPForbidden from kallithea.model.forms import GistForm from kallithea.model.gist import GistModel @@ -44,7 +44,6 @@ from kallithea.lib.utils import jsonify from kallithea.lib.utils2 import safe_int, time_to_datetime from kallithea.lib.helpers import Page -from webob.exc import HTTPNotFound, HTTPForbidden from sqlalchemy.sql.expression import or_ from kallithea.lib.vcs.exceptions import VCSError, NodeNotChangedError @@ -70,7 +69,7 @@ def index(self): """GET /admin/gists: All items in the collection""" # url('gists') - not_default_user = c.authuser.username != User.DEFAULT_USER + not_default_user = not c.authuser.is_default_user c.show_private = request.GET.get('private') and not_default_user c.show_public = request.GET.get('public') and not_default_user @@ -144,8 +143,8 @@ except Exception as e: log.error(traceback.format_exc()) h.flash(_('Error occurred during gist creation'), category='error') - return redirect(url('new_gist')) - return redirect(url('gist', gist_id=new_gist_id)) + raise HTTPFound(location=url('new_gist')) + raise HTTPFound(location=url('gist', gist_id=new_gist_id)) @LoginRequired() @NotAnonymous() @@ -185,7 +184,7 @@ else: raise HTTPForbidden() - return redirect(url('gists')) + raise HTTPFound(location=url('gists')) @LoginRequired() def show(self, gist_id, revision='tip', format='html', f_path=None): @@ -270,7 +269,7 @@ h.flash(_('Error occurred during update of gist %s') % gist_id, category='error') - return redirect(url('gist', gist_id=gist_id)) + raise HTTPFound(location=url('gist', gist_id=gist_id)) return rendered
--- a/kallithea/controllers/admin/my_account.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/controllers/admin/my_account.py Sat Nov 07 13:24:44 2015 +0100 @@ -32,8 +32,8 @@ from sqlalchemy import func from formencode import htmlfill from pylons import request, tmpl_context as c, url -from pylons.controllers.util import redirect from pylons.i18n.translation import _ +from webob.exc import HTTPFound from kallithea import EXTERN_TYPE_INTERNAL from kallithea.lib import helpers as h @@ -69,7 +69,7 @@ if c.user.username == User.DEFAULT_USER: h.flash(_("You can't edit this user since it's" " crucial for entire application"), category='warning') - return redirect(url('users')) + raise HTTPFound(location=url('users')) c.EXTERN_TYPE_INTERNAL = EXTERN_TYPE_INTERNAL def _load_my_repos_data(self, watched=False): @@ -144,7 +144,7 @@ h.flash(_('Error occurred during update of user %s') \ % form_result.get('username'), category='error') if update: - return redirect('my_account') + raise HTTPFound(location='my_account') return htmlfill.render( render('admin/my_account/my_account.html'), defaults=defaults, @@ -225,7 +225,7 @@ log.error(traceback.format_exc()) h.flash(_('An error occurred during email saving'), category='error') - return redirect(url('my_account_emails')) + raise HTTPFound(location=url('my_account_emails')) def my_account_emails_delete(self): email_id = request.POST.get('del_email_id') @@ -233,7 +233,7 @@ user_model.delete_extra_email(self.authuser.user_id, email_id) Session().commit() h.flash(_("Removed email from user"), category='success') - return redirect(url('my_account_emails')) + raise HTTPFound(location=url('my_account_emails')) def my_account_api_keys(self): c.active = 'api_keys' @@ -257,7 +257,7 @@ ApiKeyModel().create(self.authuser.user_id, description, lifetime) Session().commit() h.flash(_("API key successfully created"), category='success') - return redirect(url('my_account_api_keys')) + raise HTTPFound(location=url('my_account_api_keys')) def my_account_api_keys_delete(self): api_key = request.POST.get('del_api_key') @@ -274,4 +274,4 @@ Session().commit() h.flash(_("API key successfully deleted"), category='success') - return redirect(url('my_account_api_keys')) + raise HTTPFound(location=url('my_account_api_keys'))
--- a/kallithea/controllers/admin/notifications.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/controllers/admin/notifications.py Sat Nov 07 13:24:44 2015 +0100 @@ -30,8 +30,7 @@ from pylons import request from pylons import tmpl_context as c -from pylons.controllers.util import abort -from webob.exc import HTTPBadRequest +from webob.exc import HTTPBadRequest, HTTPForbidden from kallithea.model.db import Notification from kallithea.model.notification import NotificationModel @@ -168,7 +167,7 @@ return render('admin/notifications/show_notification.html') - return abort(403) + raise HTTPForbidden() def edit(self, notification_id, format='html'): """GET /_admin/notifications/id/edit: Form to edit an existing item"""
--- a/kallithea/controllers/admin/permissions.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/controllers/admin/permissions.py Sat Nov 07 13:24:44 2015 +0100 @@ -32,8 +32,8 @@ from formencode import htmlfill from pylons import request, tmpl_context as c, url -from pylons.controllers.util import redirect from pylons.i18n.translation import _ +from webob.exc import HTTPFound from kallithea.lib import helpers as h from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator @@ -139,7 +139,7 @@ h.flash(_('Error occurred during update of permissions'), category='error') - return redirect(url('admin_permissions')) + raise HTTPFound(location=url('admin_permissions')) c.user = User.get_default_user() defaults = {'anonymous': c.user.active}
--- a/kallithea/controllers/admin/repo_groups.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/controllers/admin/repo_groups.py Sat Nov 07 13:24:44 2015 +0100 @@ -33,8 +33,8 @@ from formencode import htmlfill from pylons import request, tmpl_context as c, url -from pylons.controllers.util import abort, redirect from pylons.i18n.translation import _, ungettext +from webob.exc import HTTPFound, HTTPForbidden, HTTPNotFound, HTTPInternalServerError import kallithea from kallithea.lib import helpers as h @@ -49,7 +49,6 @@ from kallithea.model.forms import RepoGroupForm, RepoGroupPermsForm from kallithea.model.meta import Session from kallithea.model.repo import RepoModel -from webob.exc import HTTPInternalServerError, HTTPNotFound from kallithea.lib.utils2 import safe_int from sqlalchemy.sql.expression import func @@ -189,10 +188,10 @@ % request.POST.get('group_name'), category='error') parent_group_id = form_result['group_parent_id'] #TODO: maybe we should get back to the main view, not the admin one - return redirect(url('repos_groups', parent_group=parent_group_id)) + raise HTTPFound(location=url('repos_groups', parent_group=parent_group_id)) h.flash(_('Created repository group %s') % gr.group_name, category='success') - return redirect(url('repos_group_home', group_name=gr.group_name)) + raise HTTPFound(location=url('repos_group_home', group_name=gr.group_name)) def new(self): """GET /repo_groups/new: Form to create a new item""" @@ -209,7 +208,7 @@ if HasRepoGroupPermissionAll('group.admin')(group_name, 'group create'): pass else: - return abort(403) + raise HTTPForbidden() self.__load_defaults() return render('admin/repo_groups/repo_group_add.html') @@ -266,7 +265,7 @@ h.flash(_('Error occurred during update of repository group %s') \ % request.POST.get('group_name'), category='error') - return redirect(url('edit_repo_group', group_name=group_name)) + raise HTTPFound(location=url('edit_repo_group', group_name=group_name)) @HasRepoGroupPermissionAnyDecorator('group.admin') def delete(self, group_name): @@ -283,13 +282,13 @@ if repos: h.flash(_('This group contains %s repositories and cannot be ' 'deleted') % len(repos), category='warning') - return redirect(url('repos_groups')) + raise HTTPFound(location=url('repos_groups')) children = gr.children.all() if children: h.flash(_('This group contains %s subgroups and cannot be deleted' % (len(children))), category='warning') - return redirect(url('repos_groups')) + raise HTTPFound(location=url('repos_groups')) try: RepoGroupModel().delete(group_name) @@ -303,8 +302,8 @@ % group_name, category='error') if gr.parent_group: - return redirect(url('repos_group_home', group_name=gr.parent_group.group_name)) - return redirect(url('repos_groups')) + raise HTTPFound(location=url('repos_group_home', group_name=gr.parent_group.group_name)) + raise HTTPFound(location=url('repos_groups')) def show_by_name(self, group_name): """ @@ -404,7 +403,7 @@ if self._revoke_perms_on_yourself(form_result): msg = _('Cannot revoke permission for yourself as admin') h.flash(msg, category='warning') - return redirect(url('edit_repo_group_perms', group_name=group_name)) + raise HTTPFound(location=url('edit_repo_group_perms', group_name=group_name)) recursive = form_result['recursive'] # iterate over all members(if in recursive mode) of this groups and # set the permissions ! @@ -418,7 +417,7 @@ # repo_name, self.ip_addr, self.sa) Session().commit() h.flash(_('Repository group permissions updated'), category='success') - return redirect(url('edit_repo_group_perms', group_name=group_name)) + raise HTTPFound(location=url('edit_repo_group_perms', group_name=group_name)) @HasRepoGroupPermissionAnyDecorator('group.admin') def delete_perms(self, group_name):
--- a/kallithea/controllers/admin/repos.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/controllers/admin/repos.py Sat Nov 07 13:24:44 2015 +0100 @@ -29,11 +29,10 @@ import traceback import formencode from formencode import htmlfill -from webob.exc import HTTPInternalServerError, HTTPForbidden, HTTPNotFound from pylons import request, tmpl_context as c, url -from pylons.controllers.util import redirect from pylons.i18n.translation import _ from sqlalchemy.sql.expression import func +from webob.exc import HTTPFound, HTTPInternalServerError, HTTPForbidden, HTTPNotFound from kallithea.lib import helpers as h from kallithea.lib.auth import LoginRequired, \ @@ -71,7 +70,7 @@ if repo_obj is None: h.not_mapped_error(repo_name) - return redirect(url('repos')) + raise HTTPFound(location=url('repos')) return repo_obj @@ -152,9 +151,9 @@ msg = (_('Error creating repository %s') % form_result.get('repo_name')) h.flash(msg, category='error') - return redirect(url('home')) + raise HTTPFound(location=url('home')) - return redirect(h.url('repo_creating_home', + raise HTTPFound(location=h.url('repo_creating_home', repo_name=form_result['repo_name_full'], task_id=task_id)) @@ -282,7 +281,7 @@ log.error(traceback.format_exc()) h.flash(_('Error occurred during update of repository %s') \ % repo_name, category='error') - return redirect(url('edit_repo', repo_name=changed_name)) + raise HTTPFound(location=url('edit_repo', repo_name=changed_name)) @HasRepoPermissionAllDecorator('repository.admin') def delete(self, repo_name): @@ -299,7 +298,7 @@ repo = repo_model.get_by_repo_name(repo_name) if not repo: h.not_mapped_error(repo_name) - return redirect(url('repos')) + raise HTTPFound(location=url('repos')) try: _forks = repo.forks.count() handle_forks = None @@ -327,8 +326,8 @@ category='error') if repo.group: - return redirect(url('repos_group_home', group_name=repo.group.group_name)) - return redirect(url('repos')) + raise HTTPFound(location=url('repos_group_home', group_name=repo.group.group_name)) + raise HTTPFound(location=url('repos')) @HasRepoPermissionAllDecorator('repository.admin') def edit(self, repo_name): @@ -372,7 +371,7 @@ # repo_name, self.ip_addr, self.sa) Session().commit() h.flash(_('Repository permissions updated'), category='success') - return redirect(url('edit_repo_perms', repo_name=repo_name)) + raise HTTPFound(location=url('edit_repo_perms', repo_name=repo_name)) def edit_permissions_revoke(self, repo_name): try: @@ -409,7 +408,7 @@ c.active = 'fields' if request.POST: - return redirect(url('repo_edit_fields')) + raise HTTPFound(location=url('repo_edit_fields')) return render('admin/repos/repo_edit.html') @HasRepoPermissionAllDecorator('repository.admin') @@ -431,7 +430,7 @@ if isinstance(e, formencode.Invalid): msg += ". " + e.msg h.flash(msg, category='error') - return redirect(url('edit_repo_fields', repo_name=repo_name)) + raise HTTPFound(location=url('edit_repo_fields', repo_name=repo_name)) @HasRepoPermissionAllDecorator('repository.admin') def delete_repo_field(self, repo_name, field_id): @@ -443,7 +442,7 @@ log.error(traceback.format_exc()) msg = _('An error occurred during removal of field') h.flash(msg, category='error') - return redirect(url('edit_repo_fields', repo_name=repo_name)) + raise HTTPFound(location=url('edit_repo_fields', repo_name=repo_name)) @HasRepoPermissionAllDecorator('repository.admin') def edit_advanced(self, repo_name): @@ -468,7 +467,7 @@ c.active = 'advanced' if request.POST: - return redirect(url('repo_edit_advanced')) + raise HTTPFound(location=url('repo_edit_advanced')) return htmlfill.render( render('admin/repos/repo_edit.html'), defaults=defaults, @@ -495,7 +494,7 @@ h.flash(_('An error occurred during setting this' ' repository in public journal'), category='error') - return redirect(url('edit_repo_advanced', repo_name=repo_name)) + raise HTTPFound(location=url('edit_repo_advanced', repo_name=repo_name)) @HasRepoPermissionAllDecorator('repository.admin') @@ -521,7 +520,7 @@ h.flash(_('An error occurred during this operation'), category='error') - return redirect(url('edit_repo_advanced', repo_name=repo_name)) + raise HTTPFound(location=url('edit_repo_advanced', repo_name=repo_name)) @HasRepoPermissionAllDecorator('repository.admin') def edit_advanced_locking(self, repo_name): @@ -542,7 +541,7 @@ log.error(traceback.format_exc()) h.flash(_('An error occurred during unlocking'), category='error') - return redirect(url('edit_repo_advanced', repo_name=repo_name)) + raise HTTPFound(location=url('edit_repo_advanced', repo_name=repo_name)) @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin') def toggle_locking(self, repo_name): @@ -567,7 +566,7 @@ log.error(traceback.format_exc()) h.flash(_('An error occurred during unlocking'), category='error') - return redirect(url('summary_home', repo_name=repo_name)) + raise HTTPFound(location=url('summary_home', repo_name=repo_name)) @HasRepoPermissionAllDecorator('repository.admin') def edit_caches(self, repo_name): @@ -577,7 +576,7 @@ c.active = 'caches' if request.POST: try: - ScmModel().mark_for_invalidation(repo_name, delete=True) + ScmModel().mark_for_invalidation(repo_name) Session().commit() h.flash(_('Cache invalidation successful'), category='success') @@ -586,7 +585,7 @@ h.flash(_('An error occurred during cache invalidation'), category='error') - return redirect(url('edit_repo_caches', repo_name=c.repo_name)) + raise HTTPFound(location=url('edit_repo_caches', repo_name=c.repo_name)) return render('admin/repos/repo_edit.html') @HasRepoPermissionAllDecorator('repository.admin') @@ -603,7 +602,7 @@ log.error(traceback.format_exc()) h.flash(_('An error occurred during pull from remote location'), category='error') - return redirect(url('edit_repo_remote', repo_name=c.repo_name)) + raise HTTPFound(location=url('edit_repo_remote', repo_name=c.repo_name)) return render('admin/repos/repo_edit.html') @HasRepoPermissionAllDecorator('repository.admin') @@ -636,6 +635,6 @@ log.error(traceback.format_exc()) h.flash(_('An error occurred during deletion of repository stats'), category='error') - return redirect(url('edit_repo_statistics', repo_name=c.repo_name)) + raise HTTPFound(location=url('edit_repo_statistics', repo_name=c.repo_name)) return render('admin/repos/repo_edit.html')
--- a/kallithea/controllers/admin/settings.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/controllers/admin/settings.py Sat Nov 07 13:24:44 2015 +0100 @@ -31,8 +31,8 @@ from formencode import htmlfill from pylons import request, tmpl_context as c, url, config -from pylons.controllers.util import redirect from pylons.i18n.translation import _ +from webob.exc import HTTPFound from kallithea.lib import helpers as h from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator @@ -206,7 +206,7 @@ if invalidate_cache: log.debug('invalidating all repositories cache') for repo in Repository.get_all(): - ScmModel().mark_for_invalidation(repo.repo_name, delete=True) + ScmModel().mark_for_invalidation(repo.repo_name) filesystem_repos = ScmModel().repo_scan() added, removed = repo2db_mapper(filesystem_repos, rm_obsolete, @@ -218,7 +218,7 @@ for repo_name in added) or '-', ', '.join(h.escape(safe_unicode(repo_name)) for repo_name in removed) or '-')), category='success') - return redirect(url('admin_settings_mapping')) + raise HTTPFound(location=url('admin_settings_mapping')) defaults = Setting.get_app_settings() defaults.update(self._get_hg_ui_settings()) @@ -278,7 +278,7 @@ 'application settings'), category='error') - return redirect(url('admin_settings_global')) + raise HTTPFound(location=url('admin_settings_global')) defaults = Setting.get_app_settings() defaults.update(self._get_hg_ui_settings()) @@ -336,7 +336,7 @@ 'visualisation settings'), category='error') - return redirect(url('admin_settings_visual')) + raise HTTPFound(location=url('admin_settings_visual')) defaults = Setting.get_app_settings() defaults.update(self._get_hg_ui_settings()) @@ -359,7 +359,7 @@ 'Kallithea version: %s' % c.kallithea_version) if not test_email: h.flash(_('Please enter email address'), category='error') - return redirect(url('admin_settings_email')) + raise HTTPFound(location=url('admin_settings_email')) test_email_txt_body = EmailNotificationModel()\ .get_email_tmpl(EmailNotificationModel.TYPE_DEFAULT, @@ -374,7 +374,7 @@ test_email_txt_body, test_email_html_body) h.flash(_('Send email task created'), category='success') - return redirect(url('admin_settings_email')) + raise HTTPFound(location=url('admin_settings_email')) defaults = Setting.get_app_settings() defaults.update(self._get_hg_ui_settings()) @@ -425,7 +425,7 @@ h.flash(_('Error occurred during hook creation'), category='error') - return redirect(url('admin_settings_hooks')) + raise HTTPFound(location=url('admin_settings_hooks')) defaults = Setting.get_app_settings() defaults.update(self._get_hg_ui_settings()) @@ -449,7 +449,7 @@ full_index = request.POST.get('full_index', False) run_task(tasks.whoosh_index, repo_location, full_index) h.flash(_('Whoosh reindex task scheduled'), category='success') - return redirect(url('admin_settings_search')) + raise HTTPFound(location=url('admin_settings_search')) defaults = Setting.get_app_settings() defaults.update(self._get_hg_ui_settings())
--- a/kallithea/controllers/admin/user_groups.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/controllers/admin/user_groups.py Sat Nov 07 13:24:44 2015 +0100 @@ -31,8 +31,8 @@ from formencode import htmlfill from pylons import request, tmpl_context as c, url, config -from pylons.controllers.util import redirect from pylons.i18n.translation import _ +from webob.exc import HTTPFound from sqlalchemy.orm import joinedload from sqlalchemy.sql.expression import func @@ -163,7 +163,7 @@ h.flash(_('Error occurred during creation of user group %s') \ % request.POST.get('users_group_name'), category='error') - return redirect(url('users_groups')) + raise HTTPFound(location=url('users_groups')) @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true') def new(self, format='html'): @@ -224,7 +224,7 @@ h.flash(_('Error occurred during update of user group %s') \ % request.POST.get('users_group_name'), category='error') - return redirect(url('edit_users_group', id=id)) + raise HTTPFound(location=url('edit_users_group', id=id)) @HasUserGroupPermissionAnyDecorator('usergroup.admin') def delete(self, id): @@ -246,7 +246,7 @@ log.error(traceback.format_exc()) h.flash(_('An error occurred during deletion of user group'), category='error') - return redirect(url('users_groups')) + raise HTTPFound(location=url('users_groups')) def show(self, id, format='html'): """GET /user_groups/id: Show a specific item""" @@ -312,13 +312,13 @@ form['perms_updates']) except RepoGroupAssignmentError: h.flash(_('Target group cannot be the same'), category='error') - return redirect(url('edit_user_group_perms', id=id)) + raise HTTPFound(location=url('edit_user_group_perms', id=id)) #TODO: implement this #action_logger(self.authuser, 'admin_changed_repo_permissions', # repo_name, self.ip_addr, self.sa) Session().commit() h.flash(_('User group permissions updated'), category='success') - return redirect(url('edit_user_group_perms', id=id)) + raise HTTPFound(location=url('edit_user_group_perms', id=id)) @HasUserGroupPermissionAnyDecorator('usergroup.admin') def delete_perms(self, id): @@ -444,7 +444,7 @@ h.flash(_('An error occurred during permissions saving'), category='error') - return redirect(url('edit_user_group_default_perms', id=id)) + raise HTTPFound(location=url('edit_user_group_default_perms', id=id)) @HasUserGroupPermissionAnyDecorator('usergroup.admin') def edit_advanced(self, id):
--- a/kallithea/controllers/admin/users.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/controllers/admin/users.py Sat Nov 07 13:24:44 2015 +0100 @@ -31,10 +31,9 @@ from formencode import htmlfill from pylons import request, tmpl_context as c, url, config -from pylons.controllers.util import redirect from pylons.i18n.translation import _ from sqlalchemy.sql.expression import func -from webob.exc import HTTPNotFound +from webob.exc import HTTPFound, HTTPNotFound import kallithea from kallithea.lib.exceptions import DefaultUserException, \ @@ -148,7 +147,7 @@ log.error(traceback.format_exc()) h.flash(_('Error occurred during creation of user %s') \ % request.POST.get('username'), category='error') - return redirect(url('users')) + raise HTTPFound(location=url('users')) def new(self, format='html'): """GET /users/new: Form to create a new item""" @@ -201,7 +200,7 @@ log.error(traceback.format_exc()) h.flash(_('Error occurred during update of user %s') \ % form_result.get('username'), category='error') - return redirect(url('edit_user', id=id)) + raise HTTPFound(location=url('edit_user', id=id)) def delete(self, id): """DELETE /users/id: Delete an existing item""" @@ -222,7 +221,7 @@ log.error(traceback.format_exc()) h.flash(_('An error occurred during deletion of user'), category='error') - return redirect(url('users')) + raise HTTPFound(location=url('users')) def show(self, id, format='html'): """GET /users/id: Show a specific item""" @@ -306,7 +305,7 @@ ApiKeyModel().create(c.user.user_id, description, lifetime) Session().commit() h.flash(_("API key successfully created"), category='success') - return redirect(url('edit_user_api_keys', id=c.user.user_id)) + raise HTTPFound(location=url('edit_user_api_keys', id=c.user.user_id)) def delete_api_key(self, id): c.user = self._get_user_or_raise_if_default(id) @@ -324,7 +323,7 @@ Session().commit() h.flash(_("API key successfully deleted"), category='success') - return redirect(url('edit_user_api_keys', id=c.user.user_id)) + raise HTTPFound(location=url('edit_user_api_keys', id=c.user.user_id)) def update_account(self, id): pass @@ -387,7 +386,7 @@ log.error(traceback.format_exc()) h.flash(_('An error occurred during permissions saving'), category='error') - return redirect(url('edit_user_perms', id=id)) + raise HTTPFound(location=url('edit_user_perms', id=id)) def edit_emails(self, id): c.user = self._get_user_or_raise_if_default(id) @@ -420,7 +419,7 @@ log.error(traceback.format_exc()) h.flash(_('An error occurred during email saving'), category='error') - return redirect(url('edit_user_emails', id=id)) + raise HTTPFound(location=url('edit_user_emails', id=id)) def delete_email(self, id): """DELETE /user_emails_delete/id: Delete an existing item""" @@ -431,7 +430,7 @@ user_model.delete_extra_email(id, email_id) Session().commit() h.flash(_("Removed email from user"), category='success') - return redirect(url('edit_user_emails', id=id)) + raise HTTPFound(location=url('edit_user_emails', id=id)) def edit_ips(self, id): c.user = self._get_user_or_raise_if_default(id) @@ -470,8 +469,8 @@ category='error') if 'default_user' in request.POST: - return redirect(url('admin_permissions_ips')) - return redirect(url('edit_user_ips', id=id)) + raise HTTPFound(location=url('admin_permissions_ips')) + raise HTTPFound(location=url('edit_user_ips', id=id)) def delete_ip(self, id): """DELETE /user_ips_delete/id: Delete an existing item""" @@ -483,5 +482,5 @@ h.flash(_("Removed IP address from user whitelist"), category='success') if 'default_user' in request.POST: - return redirect(url('admin_permissions_ips')) - return redirect(url('edit_user_ips', id=id)) + raise HTTPFound(location=url('admin_permissions_ips')) + raise HTTPFound(location=url('edit_user_ips', id=id))
--- a/kallithea/controllers/changelog.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/controllers/changelog.py Sat Nov 07 13:24:44 2015 +0100 @@ -29,9 +29,8 @@ import traceback from pylons import request, url, session, tmpl_context as c -from pylons.controllers.util import redirect from pylons.i18n.translation import _ -from webob.exc import HTTPNotFound, HTTPBadRequest +from webob.exc import HTTPFound, HTTPNotFound, HTTPBadRequest import kallithea.lib.helpers as h from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator @@ -99,8 +98,8 @@ if request.GET.get('set'): request.GET.pop('set', None) if revision is None: - return redirect(url('changelog_home', repo_name=repo_name, **request.GET)) - return redirect(url('changelog_file_home', repo_name=repo_name, revision=revision, f_path=f_path, **request.GET)) + raise HTTPFound(location=url('changelog_home', repo_name=repo_name, **request.GET)) + raise HTTPFound(location=url('changelog_file_home', repo_name=repo_name, revision=revision, f_path=f_path, **request.GET)) limit = 2000 default = 100 @@ -118,7 +117,7 @@ branch_name not in c.db_repo_scm_instance.branches and branch_name not in c.db_repo_scm_instance.closed_branches and not revision): - return redirect(url('changelog_file_home', repo_name=c.repo_name, + raise HTTPFound(location=url('changelog_file_home', repo_name=c.repo_name, revision=branch_name, f_path=f_path or '')) if revision == 'tip': @@ -140,7 +139,7 @@ collection = cs.get_file_history(f_path) except RepositoryError as e: h.flash(safe_str(e), category='warning') - redirect(h.url('changelog_home', repo_name=repo_name)) + raise HTTPFound(location=h.url('changelog_home', repo_name=repo_name)) collection = list(reversed(collection)) else: collection = c.db_repo_scm_instance.get_changesets(start=0, end=revision, @@ -155,11 +154,11 @@ c.statuses = c.db_repo.statuses(page_revisions) except EmptyRepositoryError as e: h.flash(safe_str(e), category='warning') - return redirect(url('summary_home', repo_name=c.repo_name)) + raise HTTPFound(location=url('summary_home', repo_name=c.repo_name)) except (RepositoryError, ChangesetDoesNotExistError, Exception) as e: log.error(traceback.format_exc()) h.flash(safe_str(e), category='error') - return redirect(url('changelog_home', repo_name=c.repo_name)) + raise HTTPFound(location=url('changelog_home', repo_name=c.repo_name)) c.branch_name = branch_name c.branch_filters = [('', _('None'))] + \
--- a/kallithea/controllers/changeset.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/controllers/changeset.py Sat Nov 07 13:24:44 2015 +0100 @@ -29,13 +29,12 @@ import logging import traceback from collections import defaultdict -from webob.exc import HTTPForbidden, HTTPBadRequest, HTTPNotFound from pylons import tmpl_context as c, request, response from pylons.i18n.translation import _ -from pylons.controllers.util import redirect +from webob.exc import HTTPFound, HTTPForbidden, HTTPBadRequest, HTTPNotFound + from kallithea.lib.utils import jsonify - from kallithea.lib.vcs.exceptions import RepositoryError, \ ChangesetDoesNotExistError @@ -383,7 +382,7 @@ msg = _('Changing status on a changeset associated with ' 'a closed pull request is not allowed') h.flash(msg, category='warning') - return redirect(h.url('changeset_home', repo_name=repo_name, + raise HTTPFound(location=h.url('changeset_home', repo_name=repo_name, revision=revision)) action_logger(self.authuser, 'user_commented_revision:%s' % revision, @@ -392,7 +391,7 @@ Session().commit() if not request.environ.get('HTTP_X_PARTIAL_XHR'): - return redirect(h.url('changeset_home', repo_name=repo_name, + raise HTTPFound(location=h.url('changeset_home', repo_name=repo_name, revision=revision)) #only ajax below data = {
--- a/kallithea/controllers/compare.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/controllers/compare.py Sat Nov 07 13:24:44 2015 +0100 @@ -30,11 +30,11 @@ import logging import re -from webob.exc import HTTPBadRequest from pylons import request, tmpl_context as c, url -from pylons.controllers.util import redirect from pylons.i18n.translation import _ +from webob.exc import HTTPFound, HTTPBadRequest +from kallithea.lib.utils2 import safe_str from kallithea.lib.vcs.utils.hgcompat import unionrepo from kallithea.lib import helpers as h from kallithea.lib.base import BaseRepoController, render @@ -114,10 +114,10 @@ from dulwich.client import SubprocessGitClient gitrepo = Repo(org_repo.path) - SubprocessGitClient(thin_packs=False).fetch(other_repo.path, gitrepo) + SubprocessGitClient(thin_packs=False).fetch(safe_str(other_repo.path), gitrepo) gitrepo_remote = Repo(other_repo.path) - SubprocessGitClient(thin_packs=False).fetch(org_repo.path, gitrepo_remote) + SubprocessGitClient(thin_packs=False).fetch(safe_str(org_repo.path), gitrepo_remote) revs = [] for x in gitrepo_remote.get_walker(include=[other_rev], @@ -206,19 +206,19 @@ msg = 'Could not find org repo %s' % org_repo log.error(msg) h.flash(msg, category='error') - return redirect(url('compare_home', repo_name=c.repo_name)) + raise HTTPFound(location=url('compare_home', repo_name=c.repo_name)) if other_repo is None: msg = 'Could not find other repo %s' % other_repo log.error(msg) h.flash(msg, category='error') - return redirect(url('compare_home', repo_name=c.repo_name)) + raise HTTPFound(location=url('compare_home', repo_name=c.repo_name)) if org_repo.scm_instance.alias != other_repo.scm_instance.alias: msg = 'compare of two different kind of remote repos not available' log.error(msg) h.flash(msg, category='error') - return redirect(url('compare_home', repo_name=c.repo_name)) + raise HTTPFound(location=url('compare_home', repo_name=c.repo_name)) c.a_rev = self._get_ref_rev(org_repo, org_ref_type, org_ref_name, returnempty=True)
--- a/kallithea/controllers/files.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/controllers/files.py Sat Nov 07 13:24:44 2015 +0100 @@ -33,9 +33,9 @@ from pylons import request, response, tmpl_context as c, url from pylons.i18n.translation import _ -from pylons.controllers.util import redirect +from webob.exc import HTTPFound + from kallithea.lib.utils import jsonify, action_logger - from kallithea.lib import diffs from kallithea.lib import helpers as h @@ -137,6 +137,7 @@ c.f_path = f_path c.annotate = annotate cur_rev = c.changeset.revision + c.fulldiff = request.GET.get('fulldiff') # prev link try: @@ -306,7 +307,7 @@ % (h.person_by_id(repo.locked[0]), h.fmt_date(h.time_to_datetime(repo.locked[1]))), 'warning') - return redirect(h.url('files_home', + raise HTTPFound(location=h.url('files_home', repo_name=repo_name, revision='tip')) # check if revision is a branch identifier- basically we cannot @@ -316,7 +317,7 @@ if revision not in _branches.keys() + _branches.values(): h.flash(_('You can only delete files with revision ' 'being a valid branch'), category='warning') - return redirect(h.url('files_home', + raise HTTPFound(location=h.url('files_home', repo_name=repo_name, revision='tip', f_path=f_path)) @@ -352,7 +353,7 @@ except Exception: log.error(traceback.format_exc()) h.flash(_('Error occurred during commit'), category='error') - return redirect(url('changeset_home', + raise HTTPFound(location=url('changeset_home', repo_name=c.repo_name, revision='tip')) return render('files/files_delete.html') @@ -366,7 +367,7 @@ % (h.person_by_id(repo.locked[0]), h.fmt_date(h.time_to_datetime(repo.locked[1]))), 'warning') - return redirect(h.url('files_home', + raise HTTPFound(location=h.url('files_home', repo_name=repo_name, revision='tip')) # check if revision is a branch identifier- basically we cannot @@ -376,7 +377,7 @@ if revision not in _branches.keys() + _branches.values(): h.flash(_('You can only edit files with revision ' 'being a valid branch'), category='warning') - return redirect(h.url('files_home', + raise HTTPFound(location=h.url('files_home', repo_name=repo_name, revision='tip', f_path=f_path)) @@ -386,7 +387,7 @@ c.file = self.__get_filenode(c.cs, f_path) if c.file.is_binary: - return redirect(url('files_home', repo_name=c.repo_name, + raise HTTPFound(location=url('files_home', repo_name=c.repo_name, revision=c.cs.raw_id, f_path=f_path)) c.default_message = _('Edited file %s via Kallithea') % (f_path) c.f_path = f_path @@ -405,7 +406,7 @@ if content == old_content: h.flash(_('No changes'), category='warning') - return redirect(url('changeset_home', repo_name=c.repo_name, + raise HTTPFound(location=url('changeset_home', repo_name=c.repo_name, revision='tip')) try: self.scm_model.commit_change(repo=c.db_repo_scm_instance, @@ -418,7 +419,7 @@ except Exception: log.error(traceback.format_exc()) h.flash(_('Error occurred during commit'), category='error') - return redirect(url('changeset_home', + raise HTTPFound(location=url('changeset_home', repo_name=c.repo_name, revision='tip')) return render('files/files_edit.html') @@ -433,7 +434,7 @@ % (h.person_by_id(repo.locked[0]), h.fmt_date(h.time_to_datetime(repo.locked[1]))), 'warning') - return redirect(h.url('files_home', + raise HTTPFound(location=h.url('files_home', repo_name=repo_name, revision='tip')) r_post = request.POST @@ -462,11 +463,11 @@ if not content: h.flash(_('No content'), category='warning') - return redirect(url('changeset_home', repo_name=c.repo_name, + raise HTTPFound(location=url('changeset_home', repo_name=c.repo_name, revision='tip')) if not filename: h.flash(_('No filename'), category='warning') - return redirect(url('changeset_home', repo_name=c.repo_name, + raise HTTPFound(location=url('changeset_home', repo_name=c.repo_name, revision='tip')) #strip all crap out of file, just leave the basename filename = os.path.basename(filename) @@ -492,14 +493,14 @@ except NonRelativePathError as e: h.flash(_('Location must be relative path and must not ' 'contain .. in path'), category='warning') - return redirect(url('changeset_home', repo_name=c.repo_name, + raise HTTPFound(location=url('changeset_home', repo_name=c.repo_name, revision='tip')) except (NodeError, NodeAlreadyExistsError) as e: h.flash(_(e), category='error') except Exception: log.error(traceback.format_exc()) h.flash(_('Error occurred during commit'), category='error') - return redirect(url('changeset_home', + raise HTTPFound(location=url('changeset_home', repo_name=c.repo_name, revision='tip')) return render('files/files_add.html') @@ -620,7 +621,7 @@ _url = url('files_home', repo_name=c.repo_name, revision=diff1, f_path=c.f_path) - return redirect(_url) + raise HTTPFound(location=_url) try: if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]: c.changeset_1 = c.db_repo_scm_instance.get_changeset(diff1) @@ -655,7 +656,7 @@ node2 = FileNode(f_path, '', changeset=c.changeset_2) except (RepositoryError, NodeError): log.error(traceback.format_exc()) - return redirect(url('files_home', repo_name=c.repo_name, + raise HTTPFound(location=url('files_home', repo_name=c.repo_name, f_path=f_path)) if c.action == 'download':
--- a/kallithea/controllers/forks.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/controllers/forks.py Sat Nov 07 13:24:44 2015 +0100 @@ -31,8 +31,8 @@ from formencode import htmlfill from pylons import tmpl_context as c, request, url -from pylons.controllers.util import redirect from pylons.i18n.translation import _ +from webob.exc import HTTPFound import kallithea.lib.helpers as h @@ -77,7 +77,7 @@ if c.repo_info is None: h.not_mapped_error(repo_name) - return redirect(url('repos')) + raise HTTPFound(location=url('repos')) c.default_user_id = User.get_default_user().user_id c.in_public_journal = UserFollowing.query()\ @@ -137,7 +137,7 @@ c.repo_info = Repository.get_by_repo_name(repo_name) if not c.repo_info: h.not_mapped_error(repo_name) - return redirect(url('home')) + raise HTTPFound(location=url('home')) defaults = self.__load_data(repo_name) @@ -186,6 +186,6 @@ h.flash(_('An error occurred during repository forking %s') % repo_name, category='error') - return redirect(h.url('repo_creating_home', + raise HTTPFound(location=h.url('repo_creating_home', repo_name=form_result['repo_name_full'], task_id=task_id))
--- a/kallithea/controllers/login.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/controllers/login.py Sat Nov 07 13:24:44 2015 +0100 @@ -31,10 +31,9 @@ import formencode from formencode import htmlfill +from pylons.i18n.translation import _ +from pylons import request, session, tmpl_context as c, url from webob.exc import HTTPFound, HTTPBadRequest -from pylons.i18n.translation import _ -from pylons.controllers.util import redirect -from pylons import request, session, tmpl_context as c, url import kallithea.lib.helpers as h from kallithea.lib.auth import AuthUser, HasPermissionAnyDecorator @@ -79,11 +78,10 @@ else: c.came_from = url('home') - not_default = self.authuser.username != User.DEFAULT_USER ip_allowed = AuthUser.check_ip_allowed(self.authuser, self.ip_addr) # redirect if already logged in - if self.authuser.is_authenticated and not_default and ip_allowed: + if self.authuser.is_authenticated and ip_allowed: raise HTTPFound(location=c.came_from) if request.POST: @@ -152,7 +150,7 @@ h.flash(_('You have successfully registered into Kallithea'), category='success') Session().commit() - return redirect(url('login_home')) + raise HTTPFound(location=url('login_home')) except formencode.Invalid as errors: return htmlfill.render( @@ -196,7 +194,7 @@ redirect_link = UserModel().send_reset_password_email(form_result) h.flash(_('A password reset confirmation code has been sent'), category='success') - return redirect(redirect_link) + raise HTTPFound(location=redirect_link) except formencode.Invalid as errors: return htmlfill.render( @@ -249,12 +247,12 @@ UserModel().reset_password(form_result['email'], form_result['password']) h.flash(_('Successfully updated password'), category='success') - return redirect(url('login_home')) + raise HTTPFound(location=url('login_home')) def logout(self): session.delete() log.info('Logging out and deleting session for user') - redirect(url('home')) + raise HTTPFound(location=url('home')) def authentication_token(self): """Return the CSRF protection token for the session - just like it
--- a/kallithea/controllers/pullrequests.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/controllers/pullrequests.py Sat Nov 07 13:24:44 2015 +0100 @@ -30,11 +30,9 @@ import formencode import re -from webob.exc import HTTPNotFound, HTTPForbidden, HTTPBadRequest - from pylons import request, tmpl_context as c, url -from pylons.controllers.util import redirect from pylons.i18n.translation import _ +from webob.exc import HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest from kallithea.lib.vcs.utils.hgcompat import unionrepo from kallithea.lib.compat import json @@ -237,7 +235,7 @@ except EmptyRepositoryError as e: h.flash(h.literal(_('There are no changesets yet')), category='warning') - redirect(url('summary_home', repo_name=org_repo.repo_name)) + raise HTTPFound(location=url('summary_home', repo_name=org_repo.repo_name)) org_rev = request.GET.get('rev_end') # rev_start is not directly useful - its parent could however be used @@ -369,9 +367,9 @@ h.flash(_('Error occurred while creating pull request'), category='error') log.error(traceback.format_exc()) - return redirect(url('pullrequest_home', repo_name=repo_name)) + raise HTTPFound(location=url('pullrequest_home', repo_name=repo_name)) - return redirect(pull_request.url()) + raise HTTPFound(location=pull_request.url()) def create_update(self, old_pull_request, updaterev, title, description, reviewers_ids): org_repo = RepoModel()._get_repo(old_pull_request.org_repo.repo_name) @@ -456,7 +454,7 @@ h.flash(_('Error occurred while creating pull request'), category='error') log.error(traceback.format_exc()) - return redirect(old_pull_request.url()) + raise HTTPFound(location=old_pull_request.url()) ChangesetCommentsModel().create( text=_('Closed, replaced by %s .') % pull_request.url(canonical=True), @@ -470,7 +468,7 @@ h.flash(_('Pull request update created'), category='success') - return redirect(pull_request.url()) + raise HTTPFound(location=pull_request.url()) # pullrequest_post for PR editing @LoginRequired() @@ -513,7 +511,7 @@ Session().commit() h.flash(_('Pull request updated'), category='success') - return redirect(pull_request.url()) + raise HTTPFound(location=pull_request.url()) @LoginRequired() @NotAnonymous() @@ -528,7 +526,7 @@ Session().commit() h.flash(_('Successfully deleted pull request'), category='success') - return redirect(url('my_pullrequests')) + raise HTTPFound(location=url('my_pullrequests')) raise HTTPForbidden() @LoginRequired() @@ -762,7 +760,7 @@ Session().commit() if not request.environ.get('HTTP_X_PARTIAL_XHR'): - return redirect(pull_request.url()) + raise HTTPFound(location=pull_request.url()) data = { 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
--- a/kallithea/controllers/summary.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/controllers/summary.py Sat Nov 07 13:24:44 2015 +0100 @@ -114,8 +114,9 @@ def index(self, repo_name): _load_changelog_summary() - username = '' - if self.authuser.username != User.DEFAULT_USER: + if self.authuser.is_default_user: + username = '' + else: username = safe_str(self.authuser.username) _def_clone_uri = _def_clone_uri_by_id = c.clone_uri_tmpl
--- a/kallithea/i18n/de/LC_MESSAGES/kallithea.po Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/i18n/de/LC_MESSAGES/kallithea.po Sat Nov 07 13:24:44 2015 +0100 @@ -11,7 +11,7 @@ "PO-Revision-Date: 2015-09-08 10:56+0200\n" "Last-Translator: Robert Rauch <mail@robertrauch.de>\n" "Language-Team: German " -"<https://hosted.weblate.org/projects/kallithea/stable/de/>\n" +"<https://hosted.weblate.org/projects/kallithea/kallithea/de/>\n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n"
--- a/kallithea/lib/auth.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/lib/auth.py Sat Nov 07 13:24:44 2015 +0100 @@ -35,12 +35,12 @@ from decorator import decorator from pylons import url, request, session -from pylons.controllers.util import abort, redirect from pylons.i18n.translation import _ from webhelpers.pylonslib import secure_form from sqlalchemy import or_ from sqlalchemy.orm.exc import ObjectDeletedError from sqlalchemy.orm import joinedload +from webob.exc import HTTPFound, HTTPBadRequest, HTTPForbidden, HTTPMethodNotAllowed from kallithea import __platform__, is_windows, is_unix from kallithea.lib.vcs.utils.lazy import LazyProperty @@ -465,8 +465,7 @@ access to Kallithea is enabled, the default user is loaded instead. `AuthUser` does not by itself authenticate users and the constructor - sets the `is_authenticated` field to False, except when falling back - to the default anonymous user (if enabled). It's up to other parts + sets the `is_authenticated` field to False. It's up to other parts of the code to check e.g. if a supplied password is correct, and if so, set `is_authenticated` to True. @@ -508,9 +507,7 @@ if not is_user_loaded: is_user_loaded = self._fill_data(self.anonymous_user) - # The anonymous user is always "logged in". - if self.user_id == self.anonymous_user.user_id: - self.is_authenticated = True + self.is_default_user = (self.user_id == self.anonymous_user.user_id) if not self.username: self.username = 'None' @@ -623,17 +620,12 @@ def __repr__(self): return "<AuthUser('id:%s[%s] auth:%s')>"\ - % (self.user_id, self.username, self.is_authenticated) - - def set_authenticated(self, authenticated=True): - if self.user_id != self.anonymous_user.user_id: - self.is_authenticated = authenticated + % (self.user_id, self.username, (self.is_authenticated or self.is_default_user)) def to_cookie(self): """ Serializes this login session to a cookie `dict`. """ return { 'user_id': self.user_id, - 'is_authenticated': self.is_authenticated, 'is_external_auth': self.is_external_auth, } @@ -647,9 +639,7 @@ user_id=cookie.get('user_id'), is_external_auth=cookie.get('is_external_auth', False), ) - if not au.is_authenticated and au.user_id is not None: - # user is not authenticated and not empty - au.set_authenticated(cookie.get('is_authenticated')) + au.is_authenticated = True return au @classmethod @@ -716,7 +706,7 @@ if message: h.flash(h.literal(message), category='warning') log.debug('Redirecting to login page, origin: %s', p) - return redirect(url('login_home', came_from=p)) + raise HTTPFound(location=url('login_home', came_from=p)) class LoginRequired(object): @@ -758,13 +748,13 @@ else: # controller does not allow API access log.warning('API access to %s is not allowed', loc) - return abort(403) + raise HTTPForbidden() # Only allow the following HTTP request methods. (We sometimes use POST # requests with a '_method' set to 'PUT' or 'DELETE'; but that is only # used for the route lookup, and does not affect request.method.) if request.method not in ['GET', 'HEAD', 'POST', 'PUT']: - return abort(405) + raise HTTPMethodNotAllowed() # Make sure CSRF token never appears in the URL. If so, invalidate it. if secure_form.token_key in request.GET: @@ -785,17 +775,17 @@ token = request.POST.get(secure_form.token_key) if not token or token != secure_form.authentication_token(): log.error('CSRF check failed') - return abort(403) + raise HTTPForbidden() # WebOb already ignores request payload parameters for anything other # than POST/PUT, but double-check since other Kallithea code relies on # this assumption. if request.method not in ['POST', 'PUT'] and request.POST: log.error('%r request with payload parameters; WebOb should have stopped this', request.method) - return abort(400) + raise HTTPBadRequest() # regular user authentication - if user.is_authenticated: + if user.is_authenticated or user.is_default_user: log.info('user %s authenticated with regular auth @ %s', user, loc) return func(*fargs, **fkwargs) else: @@ -816,9 +806,7 @@ log.debug('Checking if user is not anonymous @%s', cls) - anonymous = self.user.username == User.DEFAULT_USER - - if anonymous: + if self.user.is_default_user: return redirect_to_login(_('You need to be a registered user to ' 'perform this action')) else: @@ -848,13 +836,10 @@ else: log.debug('Permission denied for %s %s', cls, self.user) - anonymous = self.user.username == User.DEFAULT_USER - - if anonymous: + if self.user.is_default_user: return redirect_to_login(_('You need to be signed in to view this page')) else: - # redirect with forbidden ret code - return abort(403) + raise HTTPForbidden() def check_permissions(self): """Dummy function for overriding"""
--- a/kallithea/lib/base.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/lib/base.py Sat Nov 07 13:24:44 2015 +0100 @@ -40,7 +40,6 @@ from pylons import config, tmpl_context as c, request, session, url from pylons.controllers import WSGIController -from pylons.controllers.util import redirect from pylons.templating import render_mako as render # don't remove this import from pylons.i18n.translation import _ @@ -117,7 +116,9 @@ auth_user = AuthUser(dbuser=user, is_external_auth=is_external_auth) - auth_user.set_authenticated() + # It should not be possible to explicitly log in as the default user. + assert not auth_user.is_default_user + auth_user.is_authenticated = True # Start new session to prevent session fixation attacks. session.invalidate() @@ -392,7 +393,9 @@ # Authenticate by session cookie # In ancient login sessions, 'authuser' may not be a dict. # In that case, the user will have to log in again. - if isinstance(session_authuser, dict): + # v0.3 and earlier included an 'is_authenticated' key; if present, + # this must be True. + if isinstance(session_authuser, dict) and session_authuser.get('is_authenticated', True): try: return AuthUser.from_cookie(session_authuser) except UserCreationError as e: @@ -479,7 +482,7 @@ if route in ['repo_creating_home']: return check_url = url('repo_creating_home', repo_name=c.repo_name) - return redirect(check_url) + raise webob.exc.HTTPFound(location=check_url) dbr = c.db_repo = _dbr c.db_repo_scm_instance = c.db_repo.scm_instance
--- a/kallithea/lib/compat.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/lib/compat.py Sat Nov 07 13:24:44 2015 +0100 @@ -540,7 +540,8 @@ return cmp(type(self), type(other)) return cmp(list(self), list(other)) - def __repr__(self, _track=[]): + def __repr__(self, _track=None): + _track = _track or [] if id(self) in _track: return '...' _track.append(id(self)) @@ -560,8 +561,9 @@ def __copy__(self): return self.__class__(self) - def __deepcopy__(self, memo={}): + def __deepcopy__(self, memo=None): from copy import deepcopy + memo = memo or {} result = self.__class__() memo[id(self)] = result result.__init__(deepcopy(tuple(self), memo))
--- a/kallithea/lib/db_manage.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/lib/db_manage.py Sat Nov 07 13:24:44 2015 +0100 @@ -61,14 +61,14 @@ class DbManage(object): - def __init__(self, log_sql, dbconf, root, tests=False, SESSION=None, cli_args={}): + def __init__(self, log_sql, dbconf, root, tests=False, SESSION=None, cli_args=None): self.dbname = dbconf.split('/')[-1] self.tests = tests self.root = root self.dburi = dbconf self.log_sql = log_sql self.db_exists = False - self.cli_args = cli_args + self.cli_args = cli_args or {} self.init_db(SESSION=SESSION) force_ask = self.cli_args.get('force_ask')
--- a/kallithea/lib/helpers.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/lib/helpers.py Sat Nov 07 13:24:44 2015 +0100 @@ -979,18 +979,17 @@ show_if_single_page=False, separator=' ', onclick=None, symbol_first='<<', symbol_last='>>', symbol_previous='<', symbol_next='>', - link_attr={'class': 'pager_link', 'rel': 'prerender'}, - curpage_attr={'class': 'pager_curpage'}, - dotdot_attr={'class': 'pager_dotdot'}, **kwargs): - - self.curpage_attr = curpage_attr + link_attr=None, + curpage_attr=None, + dotdot_attr=None, **kwargs): + self.curpage_attr = curpage_attr or {'class': 'pager_curpage'} self.separator = separator self.pager_kwargs = kwargs self.page_param = page_param self.partial_param = partial_param self.onclick = onclick - self.link_attr = link_attr - self.dotdot_attr = dotdot_attr + self.link_attr = link_attr or {'class': 'pager_link', 'rel': 'prerender'} + self.dotdot_attr = dotdot_attr or {'class': 'pager_dotdot'} # Don't show navigator if there is no more than one page if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
--- a/kallithea/lib/indexers/daemon.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/lib/indexers/daemon.py Sat Nov 07 13:24:44 2015 +0100 @@ -41,7 +41,7 @@ project_path = dn(dn(dn(dn(os.path.realpath(__file__))))) sys.path.append(project_path) -from kallithea.config.conf import INDEX_EXTENSIONS +from kallithea.config.conf import INDEX_EXTENSIONS, INDEX_FILENAMES from kallithea.model.scm import ScmModel from kallithea.model.db import Repository from kallithea.lib.utils2 import safe_unicode, safe_str @@ -162,6 +162,13 @@ node = cs.get_node(node_path) return node + def is_indexable_node(self, node): + """ + Just index the content of chosen files, skipping binary files + """ + return (node.extension in INDEX_EXTENSIONS or node.name in INDEX_FILENAMES) and \ + not node.is_binary + def get_node_mtime(self, node): return mktime(node.last_changeset.date.timetuple()) @@ -173,8 +180,7 @@ node = self.get_node(repo, path, index_rev) indexed = indexed_w_content = 0 - # we just index the content of chosen files, and skip binary files - if node.extension in INDEX_EXTENSIONS and not node.is_binary: + if self.is_indexable_node(node): u_content = node.content if not isinstance(u_content, unicode): log.warning(' >> %s Could not get this content as unicode '
--- a/kallithea/lib/middleware/simplegit.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/lib/middleware/simplegit.py Sat Nov 07 13:24:44 2015 +0100 @@ -294,12 +294,11 @@ if action == 'pull' and _hooks.get(Ui.HOOK_PULL): log_pull_action(ui=baseui, repo=_repo._repo) - def __inject_extras(self, repo_path, baseui, extras={}): + def __inject_extras(self, repo_path, baseui, extras=None): """ Injects some extra params into baseui instance :param baseui: baseui instance :param extras: dict with extra params to put into baseui """ - - _set_extras(extras) + _set_extras(extras or {})
--- a/kallithea/lib/middleware/simplehg.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/lib/middleware/simplehg.py Sat Nov 07 13:24:44 2015 +0100 @@ -272,7 +272,7 @@ raise Exception('Unable to detect pull/push action !!' 'Are you using non standard command or client ?') - def __inject_extras(self, repo_path, baseui, extras={}): + def __inject_extras(self, repo_path, baseui, extras=None): """ Injects some extra params into baseui instance @@ -291,4 +291,4 @@ for section in ui_sections: for k, v in repoui.configitems(section): baseui.setconfig(section, k, v) - _set_extras(extras) + _set_extras(extras or {})
--- a/kallithea/lib/rcmail/smtp_mailer.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/lib/rcmail/smtp_mailer.py Sat Nov 07 13:24:44 2015 +0100 @@ -60,9 +60,9 @@ self.debug = debug self.auth = smtp_auth - def send(self, recipients=[], subject='', body='', html='', + def send(self, recipients=None, subject='', body='', html='', attachment_files=None, headers=None): - + recipients = recipients or [] if isinstance(recipients, basestring): recipients = [recipients] if headers is None:
--- a/kallithea/lib/utils.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/lib/utils.py Sat Nov 07 13:24:44 2015 +0100 @@ -418,6 +418,21 @@ 'utf8'), sep=',') +def set_indexer_config(config): + """ + Update Whoosh index mapping + + :param config: kallithea.CONFIG + """ + from kallithea.config import conf + + log.debug('adding extra into INDEX_EXTENSIONS') + conf.INDEX_EXTENSIONS.extend(re.split('\s+', config.get('index.extensions', ''))) + + log.debug('adding extra into INDEX_FILENAMES') + conf.INDEX_FILENAMES.extend(re.split('\s+', config.get('index.filenames', ''))) + + def map_groups(path): """ Given a full path to a repository, create all nested groups that this
--- a/kallithea/lib/utils2.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/lib/utils2.py Sat Nov 07 13:24:44 2015 +0100 @@ -78,6 +78,25 @@ return dict(d) +def __get_index_filenames(): + """ + Get list of known indexable filenames from pygment lexer internals + """ + from pygments import lexers + from itertools import ifilter + + filenames = [] + + def likely_filename(s): + return s.find('*') == -1 and s.find('[') == -1 + + for lx, t in sorted(lexers.LEXERS.items()): + for f in ifilter(likely_filename, t[-2]): + filenames.append(f) + + return filenames + + def str2bool(_str): """ returs True/False value from given string, it tries to translate the
--- a/kallithea/lib/vcs/subprocessio.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/lib/vcs/subprocessio.py Sat Nov 07 13:24:44 2015 +0100 @@ -156,8 +156,8 @@ """ def __init__(self, source, buffer_size=65536, chunk_size=4096, - starting_values=[], bottomless=False): - + starting_values=None, bottomless=False): + starting_values = starting_values or [] if bottomless: maxlen = int(buffer_size / chunk_size) else: @@ -326,7 +326,7 @@ """ def __init__(self, cmd, inputstream=None, buffer_size=65536, - chunk_size=4096, starting_values=[], **kwargs): + chunk_size=4096, starting_values=None, **kwargs): """ Initializes SubprocessIOChunker @@ -336,7 +336,7 @@ :param chunk_size: (Default: 4096) A max size of a chunk. Actual chunk may be smaller. :param starting_values: (Default: []) An array of strings to put in front of output que. """ - + starting_values = starting_values or [] if inputstream: input_streamer = StreamFeeder(inputstream) input_streamer.start()
--- a/kallithea/lib/vcs/utils/compat.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/lib/vcs/utils/compat.py Sat Nov 07 13:24:44 2015 +0100 @@ -120,7 +120,8 @@ return cmp(type(self), type(other)) return cmp(list(self), list(other)) - def __repr__(self, _track=[]): + def __repr__(self, _track=None): + _track = _track or [] if id(self) in _track: return '...' _track.append(id(self)) @@ -140,8 +141,9 @@ def __copy__(self): return self.__class__(self) - def __deepcopy__(self, memo={}): + def __deepcopy__(self, memo=None): from copy import deepcopy + memo = memo or {} result = self.__class__() memo[id(self)] = result result.__init__(deepcopy(tuple(self), memo))
--- a/kallithea/model/comment.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/model/comment.py Sat Nov 07 13:24:44 2015 +0100 @@ -266,10 +266,10 @@ q = q.filter(ChangesetComment.line_no == None)\ .filter(ChangesetComment.f_path == None) - if revision: + if revision is not None: q = q.filter(ChangesetComment.revision == revision)\ .filter(ChangesetComment.repo_id == repo_id) - elif pull_request: + elif pull_request is not None: pull_request = self.__get_pull_request(pull_request) q = q.filter(ChangesetComment.pull_request == pull_request) else:
--- a/kallithea/model/db.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/model/db.py Sat Nov 07 13:24:44 2015 +0100 @@ -2103,7 +2103,7 @@ return "%s%s" % (prefix, key) @classmethod - def set_invalidate(cls, repo_name, delete=False): + def set_invalidate(cls, repo_name): """ Mark all caches of a repo as invalid in the database. """ @@ -2114,11 +2114,7 @@ for inv_obj in inv_objs: log.debug('marking %s key for invalidation based on repo_name=%s', inv_obj, safe_str(repo_name)) - if delete: - Session().delete(inv_obj) - else: - inv_obj.cache_active = False - Session().add(inv_obj) + Session().delete(inv_obj) Session().commit() @classmethod @@ -2416,11 +2412,15 @@ notification.type_ = type_ notification.created_on = datetime.datetime.now() - for u in recipients: - assoc = UserNotification() - assoc.notification = notification - assoc.user_id = u.user_id - Session().add(assoc) + for recipient in recipients: + un = UserNotification() + un.notification = notification + un.user_id = recipient.user_id + # Mark notifications to self "pre-read" - should perhaps just be skipped + if recipient == created_by: + un.read = True + Session().add(un) + Session().add(notification) Session().flush() # assign notificaiton.notification_id return notification
--- a/kallithea/model/forms.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/model/forms.py Sat Nov 07 13:24:44 2015 +0100 @@ -86,7 +86,8 @@ return _PasswordChangeForm -def UserForm(edit=False, old_data={}): +def UserForm(edit=False, old_data=None): + old_data = old_data or {} class _UserForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = True @@ -125,7 +126,9 @@ return _UserForm -def UserGroupForm(edit=False, old_data={}, available_members=[]): +def UserGroupForm(edit=False, old_data=None, available_members=None): + old_data = old_data or {} + available_members = available_members or [] class _UserGroupForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = True @@ -148,8 +151,10 @@ return _UserGroupForm -def RepoGroupForm(edit=False, old_data={}, repo_groups=[], +def RepoGroupForm(edit=False, old_data=None, repo_groups=None, can_create_in_root=False): + old_data = old_data or {} + repo_groups = repo_groups or [] repo_group_ids = [rg[0] for rg in repo_groups] class _RepoGroupForm(formencode.Schema): allow_extra_fields = True @@ -178,7 +183,7 @@ return _RepoGroupForm -def RegisterForm(edit=False, old_data={}): +def RegisterForm(edit=False, old_data=None): class _RegisterForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = True @@ -227,8 +232,11 @@ 'password_confirm')] return _PasswordResetConfirmationForm -def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(), - repo_groups=[], landing_revs=[]): +def RepoForm(edit=False, old_data=None, supported_backends=BACKENDS.keys(), + repo_groups=None, landing_revs=None): + old_data = old_data or {} + repo_groups = repo_groups or [] + landing_revs = landing_revs or [] repo_group_ids = [rg[0] for rg in repo_groups] class _RepoForm(formencode.Schema): allow_extra_fields = True @@ -302,8 +310,11 @@ return _RepoFieldForm -def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(), - repo_groups=[], landing_revs=[]): +def RepoForkForm(edit=False, old_data=None, supported_backends=BACKENDS.keys(), + repo_groups=None, landing_revs=None): + old_data = old_data or {} + repo_groups = repo_groups or [] + landing_revs = landing_revs or [] repo_group_ids = [rg[0] for rg in repo_groups] class _RepoForkForm(formencode.Schema): allow_extra_fields = True @@ -421,7 +432,7 @@ return _CustomDefaultPermissionsForm -def DefaultsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()): +def DefaultsForm(edit=False, old_data=None, supported_backends=BACKENDS.keys()): class _DefaultsForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = True
--- a/kallithea/model/notification.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/model/notification.py Sat Nov 07 13:24:44 2015 +0100 @@ -59,7 +59,7 @@ def create(self, created_by, subject, body, recipients=None, type_=Notification.TYPE_MESSAGE, with_email=True, - email_kwargs={}): + email_kwargs=None): """ Creates notification of given type @@ -75,7 +75,7 @@ :param email_kwargs: additional dict to pass as args to email template """ from kallithea.lib.celerylib import tasks, run_task - + email_kwargs = email_kwargs or {} if recipients and not getattr(recipients, '__iter__', False): raise Exception('recipients must be a list or iterable')
--- a/kallithea/model/scm.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/model/scm.py Sat Nov 07 13:24:44 2015 +0100 @@ -331,13 +331,13 @@ .filter(RepoGroup.group_parent_id == None).all() return [x for x in RepoGroupList(all_groups)] - def mark_for_invalidation(self, repo_name, delete=False): + def mark_for_invalidation(self, repo_name): """ Mark caches of this repo invalid in the database. :param repo_name: the repo for which caches should be marked invalid """ - CacheInvalidation.set_invalidate(repo_name, delete=delete) + CacheInvalidation.set_invalidate(repo_name) repo = Repository.get_by_repo_name(repo_name) if repo is not None: repo.update_changeset_cache()
--- a/kallithea/model/user.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/model/user.py Sat Nov 07 13:24:44 2015 +0100 @@ -206,9 +206,9 @@ type_=Notification.TYPE_REGISTRATION, email_kwargs=email_kwargs) - def update(self, user_id, form_data, skip_attrs=[]): + def update(self, user_id, form_data, skip_attrs=None): from kallithea.lib.auth import get_crypt_password - + skip_attrs = skip_attrs or [] user = self.get(user_id, cache=False) if user.username == User.DEFAULT_USER: raise DefaultUserException(
--- a/kallithea/model/validators.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/model/validators.py Sat Nov 07 13:24:44 2015 +0100 @@ -89,7 +89,8 @@ return _UniqueListFromString -def ValidUsername(edit=False, old_data={}): +def ValidUsername(edit=False, old_data=None): + old_data = old_data or {} class _validator(formencode.validators.FancyValidator): messages = { 'username_exists': _('Username "%(username)s" already exists'), @@ -146,7 +147,8 @@ return _validator -def ValidUserGroup(edit=False, old_data={}): +def ValidUserGroup(edit=False, old_data=None): + old_data = old_data or {} class _validator(formencode.validators.FancyValidator): messages = { 'invalid_group': _('Invalid user group name'), @@ -187,7 +189,9 @@ return _validator -def ValidRepoGroup(edit=False, old_data={}): +def ValidRepoGroup(edit=False, old_data=None): + old_data = old_data or {} + class _validator(formencode.validators.FancyValidator): messages = { 'group_parent_id': _('Cannot assign this group as parent'), @@ -338,7 +342,9 @@ return _validator -def ValidRepoName(edit=False, old_data={}): +def ValidRepoName(edit=False, old_data=None): + old_data = old_data or {} + class _validator(formencode.validators.FancyValidator): messages = { 'invalid_repo_name': @@ -373,7 +379,6 @@ return value def validate_python(self, value, state): - repo_name = value.get('repo_name') repo_name_full = value.get('repo_name_full') group_path = value.get('group_path') @@ -483,7 +488,9 @@ return _validator -def ValidForkType(old_data={}): +def ValidForkType(old_data=None): + old_data = old_data or {} + class _validator(formencode.validators.FancyValidator): messages = { 'invalid_fork_type': _('Fork has to be the same type as parent') @@ -699,7 +706,9 @@ return _validator -def UniqSystemEmail(old_data={}): +def UniqSystemEmail(old_data=None): + old_data = old_data or {} + class _validator(formencode.validators.FancyValidator): messages = { 'email_taken': _('This email address is already in use')
--- a/kallithea/public/css/style.css Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/public/css/style.css Sat Nov 07 13:24:44 2015 +0100 @@ -4346,9 +4346,6 @@ color: #666; font-size: 16px; } -.inline-comments-button .add-comment { - margin: 2px 0px 8px 5px !important -} input.status_change_radio { margin: 2px 0 5px 15px; @@ -4557,6 +4554,19 @@ overflow: hidden; } +.compare-revision-selector { + font-weight: bold; + font-size: 14px; +} +.compare-revision-selector > div { + display: inline-block; + margin-left: 8px; + vertical-align: middle; +} +.compare-revision-selector .btn { + margin-bottom: 0; +} + div.diffblock { overflow: auto; @@ -4572,6 +4582,7 @@ div.diffblock.margined { margin: 0px 20px 0px 20px; } +.compare-revision-selector, div.diffblock .code-header { border-bottom: 1px solid #CCCCCC; background: #EEEEEE;
--- a/kallithea/public/js/base.js Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/public/js/base.js Sat Nov 07 13:24:44 2015 +0100 @@ -377,15 +377,16 @@ ; }; -var ajaxGET = function(url,success) { +var ajaxGET = function(url, success, failure) { + if(failure === undefined) { + failure = function(jqXHR, textStatus, errorThrown) { + if (textStatus != "abort") + alert("Ajax GET error: " + textStatus); + }; + } return $.ajax({url: url, headers: {'X-PARTIAL-XHR': '1'}, cache: false}) .done(success) - .fail(function(jqXHR, textStatus, errorThrown) { - if (textStatus == "abort") - return; - alert("Ajax GET error: " + textStatus); - }) - ; + .fail(failure); }; var ajaxPOST = function(url, postData, success, failure) {
--- a/kallithea/templates/about.html Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/templates/about.html Sat Nov 07 13:24:44 2015 +0100 @@ -49,6 +49,7 @@ <li>Copyright © 2015, Étienne Gilli</li> <li>Copyright © 2015, Grzegorz Krason</li> <li>Copyright © 2015, Jan Heylen</li> + <li>Copyright © 2015, Jiří Suchan</li> <li>Copyright © 2015, Kazunari Kobayashi</li> <li>Copyright © 2015, Kevin Bullock</li> <li>Copyright © 2015, kobanari</li>
--- a/kallithea/templates/admin/repos/repo_edit_caches.html Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/templates/admin/repos/repo_edit_caches.html Sat Nov 07 13:24:44 2015 +0100 @@ -1,7 +1,7 @@ ${h.form(url('edit_repo_caches', repo_name=c.repo_name), method='put')} <div class="form"> <div class="fields"> - ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate Repository Cache'),class_="btn btn-small",onclick="return confirm('"+_('Confirm to invalidate repository cache.')+"');")} + ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate Repository Cache'),class_="btn btn-small")} <div class="field" style="border:none;color:#888"> <ul> <li>${_('Manually invalidate cache for this repository. On first access, the repository will be cached again.')}
--- a/kallithea/templates/base/base.html Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/templates/base/base.html Sat Nov 07 13:24:44 2015 +0100 @@ -353,95 +353,97 @@ </li> <script type="text/javascript"> - var visual_show_public_icon = "${c.visual.show_public_icon}" == "True"; - var cache = {} - /*format the look of items in the list*/ - var format = function(state){ - if (!state.id){ - return state.text; // optgroup - } - var obj_dict = state.obj; - var tmpl = ''; - - if(obj_dict && state.type == 'repo'){ - tmpl += '<span class="repo-icons">'; - if(obj_dict['repo_type'] === 'hg'){ - tmpl += '<span class="repotag">hg</span> '; + $(document).ready(function(){ + var visual_show_public_icon = "${c.visual.show_public_icon}" == "True"; + var cache = {} + /*format the look of items in the list*/ + var format = function(state){ + if (!state.id){ + return state.text; // optgroup } - else if(obj_dict['repo_type'] === 'git'){ - tmpl += '<span class="repotag">git</span> '; - } - if(obj_dict['private']){ - tmpl += '<i class="icon-keyhole-circled"></i> '; - } - else if(visual_show_public_icon){ - tmpl += '<i class="icon-globe"></i> '; - } - tmpl += '</span>'; - } - if(obj_dict && state.type == 'group'){ - tmpl += '<i class="icon-folder"></i> '; - } - tmpl += state.text; - return tmpl; - } + var obj_dict = state.obj; + var tmpl = ''; - $("#repo_switcher").select2({ - placeholder: '<i class="icon-database"></i> ${_('Repositories')}', - dropdownAutoWidth: true, - formatResult: format, - formatSelection: format, - formatNoMatches: function(term){ - return "${_('No matches found')}"; - }, - containerCssClass: "repo-switcher", - dropdownCssClass: "repo-switcher-dropdown", - escapeMarkup: function(m){ - // don't escape our custom placeholder - if(m.substr(0,29) == '<i class="icon-database"></i>'){ - return m; + if(obj_dict && state.type == 'repo'){ + tmpl += '<span class="repo-icons">'; + if(obj_dict['repo_type'] === 'hg'){ + tmpl += '<span class="repotag">hg</span> '; + } + else if(obj_dict['repo_type'] === 'git'){ + tmpl += '<span class="repotag">git</span> '; + } + if(obj_dict['private']){ + tmpl += '<i class="icon-keyhole-circled"></i> '; + } + else if(visual_show_public_icon){ + tmpl += '<i class="icon-globe"></i> '; + } + tmpl += '</span>'; } + if(obj_dict && state.type == 'group'){ + tmpl += '<i class="icon-folder"></i> '; + } + tmpl += state.text; + return tmpl; + } - return Select2.util.escapeMarkup(m); - }, - query: function(query){ - var key = 'cache'; - var cached = cache[key] ; - if(cached) { - var data = {results: []}; - //filter results - $.each(cached.results, function(){ - var section = this.text; - var children = []; - $.each(this.children, function(){ - if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){ - children.push({'id': this.id, 'text': this.text, 'type': this.type, 'obj': this.obj}); - } - }); - if(children.length !== 0){ - data.results.push({'text': section, 'children': children}); + $("#repo_switcher").select2({ + placeholder: '<i class="icon-database"></i> ${_('Repositories')}', + dropdownAutoWidth: true, + formatResult: format, + formatSelection: format, + formatNoMatches: function(term){ + return "${_('No matches found')}"; + }, + containerCssClass: "repo-switcher", + dropdownCssClass: "repo-switcher-dropdown", + escapeMarkup: function(m){ + // don't escape our custom placeholder + if(m.substr(0,29) == '<i class="icon-database"></i>'){ + return m; } - }); - query.callback(data); - }else{ - $.ajax({ - url: "${h.url('repo_switcher_data')}", - data: {}, - dataType: 'json', - type: 'GET', - success: function(data) { - cache[key] = data; - query.callback({results: data.results}); - } - }); - } - } - }); + return Select2.util.escapeMarkup(m); + }, + query: function(query){ + var key = 'cache'; + var cached = cache[key] ; + if(cached) { + var data = {results: []}; + //filter results + $.each(cached.results, function(){ + var section = this.text; + var children = []; + $.each(this.children, function(){ + if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){ + children.push({'id': this.id, 'text': this.text, 'type': this.type, 'obj': this.obj}); + } + }); + if(children.length !== 0){ + data.results.push({'text': section, 'children': children}); + } - $("#repo_switcher").on('select2-selecting', function(e){ - e.preventDefault(); - window.location = pyroutes.url('summary_home', {'repo_name': e.val}); + }); + query.callback(data); + }else{ + $.ajax({ + url: "${h.url('repo_switcher_data')}", + data: {}, + dataType: 'json', + type: 'GET', + success: function(data) { + cache[key] = data; + query.callback({results: data.results}); + } + }); + } + } + }); + + $("#repo_switcher").on('select2-selecting', function(e){ + e.preventDefault(); + window.location = pyroutes.url('summary_home', {'repo_name': e.val}); + }); }); ## Global mouse bindings ##
--- a/kallithea/templates/changeset/changeset.html Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/templates/changeset/changeset.html Sat Nov 07 13:24:44 2015 +0100 @@ -217,10 +217,8 @@ var boxid = $(target).attr('id_for'); if(target.checked){ $('#{0} .inline-comments'.format(boxid)).show(); - $('#{0} .inline-comments-button'.format(boxid)).show(); }else{ $('#{0} .inline-comments'.format(boxid)).hide(); - $('#{0} .inline-comments-button'.format(boxid)).hide(); } });
--- a/kallithea/templates/changeset/diff_block.html Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/templates/changeset/diff_block.html Sat Nov 07 13:24:44 2015 +0100 @@ -77,19 +77,19 @@ ## TODO: link to ancestor and head of other instead of exactly other %if op == 'A': ${_('Added')} - <a class="spantag" href="${h.url('files_home', repo_name=c.cs_repo.repo_name, f_path=filenode_path, revision=c.cs_rev)}">${h.short_id(c.cs_ref_name) if c.cs_ref_type=='rev' else c.cs_ref_name}</a> + <a class="spantag" href="${h.url('files_home', repo_name=c.cs_repo.repo_name, f_path=filenode_path, revision=c.cs_rev)}">${h.short_ref(c.cs_ref_type, c.cs_ref_name)}</a> %elif op == 'M': - <a class="spantag" href="${h.url('files_home', repo_name=c.a_repo.repo_name, f_path=filenode_path, revision=c.a_rev)}">${h.short_id(c.a_ref_name) if c.a_ref_type=='rev' else c.a_ref_name}</a> + <a class="spantag" href="${h.url('files_home', repo_name=c.a_repo.repo_name, f_path=filenode_path, revision=c.a_rev)}">${h.short_ref(c.a_ref_type, c.a_ref_name)}</a> <i class="icon-right"></i> - <a class="spantag" href="${h.url('files_home', repo_name=c.cs_repo.repo_name, f_path=filenode_path, revision=c.cs_rev)}">${h.short_id(c.cs_ref_name) if c.cs_ref_type=='rev' else c.cs_ref_name}</a> + <a class="spantag" href="${h.url('files_home', repo_name=c.cs_repo.repo_name, f_path=filenode_path, revision=c.cs_rev)}">${h.short_ref(c.cs_ref_type, c.cs_ref_name)}</a> %elif op == 'D': ${_('Deleted')} - <a class="spantag" href="${h.url('files_home', repo_name=c.a_repo.repo_name, f_path=filenode_path, revision=c.a_rev)}">${h.short_id(c.a_ref_name) if c.a_ref_type=='rev' else c.a_ref_name}</a> + <a class="spantag" href="${h.url('files_home', repo_name=c.a_repo.repo_name, f_path=filenode_path, revision=c.a_rev)}">${h.short_ref(c.a_ref_type, c.a_ref_name)}</a> %elif op == 'R': ${_('Renamed')} - <a class="spantag" href="${h.url('files_home', repo_name=c.a_repo.repo_name, f_path=filenode_path, revision=c.a_rev)}">${h.short_id(c.a_ref_name) if c.a_ref_type=='rev' else c.a_ref_name}</a> + <a class="spantag" href="${h.url('files_home', repo_name=c.a_repo.repo_name, f_path=filenode_path, revision=c.a_rev)}">${h.short_ref(c.a_ref_type, c.a_ref_name)}</a> <i class="icon-right"></i> - <a class="spantag" href="${h.url('files_home', repo_name=c.cs_repo.repo_name, f_path=filenode_path, revision=c.cs_rev)}">${h.short_id(c.cs_ref_name) if c.cs_ref_type=='rev' else c.cs_ref_name}</a> + <a class="spantag" href="${h.url('files_home', repo_name=c.cs_repo.repo_name, f_path=filenode_path, revision=c.cs_rev)}">${h.short_ref(c.cs_ref_type, c.cs_ref_name)}</a> %else: ${op}??? %endif
--- a/kallithea/templates/compare/compare_diff.html Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/templates/compare/compare_diff.html Sat Nov 07 13:24:44 2015 +0100 @@ -26,9 +26,15 @@ </div> <div class="table"> <div id="body" class="diffblock"> - <div class="code-header"> + <div class="compare-revision-selector"> + ## divs are "inline-block" and cannot have whitespace between them. <div> - ${h.hidden('compare_org')} <i class="icon-right"></i> ${h.hidden('compare_other')} + ${h.hidden('compare_org')} + </div><div> + <i class="icon-right"></i> + </div><div> + ${h.hidden('compare_other')} + </div><div> %if not c.compare_home: <a class="btn btn-small" href="${c.swap_url}"><i class="icon-arrows-cw"></i> ${_('Swap')}</a> %endif
--- a/kallithea/templates/files/files_source.html Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/templates/files/files_source.html Sat Nov 07 13:24:44 2015 +0100 @@ -63,15 +63,21 @@ ${_('Binary file (%s)') % c.file.mimetype} </div> %else: - %if c.file.size < c.cut_off_limit: + %if c.file.size < c.cut_off_limit or c.fulldiff: %if c.annotate: ${h.pygmentize_annotation(c.repo_name,c.file,linenos=True,anchorlinenos=True,lineanchors='L',cssclass="code-highlight")} %else: ${h.pygmentize(c.file,linenos=True,anchorlinenos=True,lineanchors='L',cssclass="code-highlight")} %endif %else: - ${_('File is too big to display')} ${h.link_to(_('Show as raw'), - h.url('files_raw_home',repo_name=c.repo_name,revision=c.file_changeset.raw_id,f_path=c.f_path))} + <h4> + ${_('File is too big to display.')} + %if c.annotate: + ${h.link_to(_('Show full annotation anyway.'), h.url.current(fulldiff=1, **request.GET.mixed()))} + %else: + ${h.link_to(_('Show as raw.'), h.url('files_raw_home',repo_name=c.repo_name,revision=c.file_changeset.raw_id,f_path=c.f_path))} + %endif + </h4> %endif %endif </div>
--- a/kallithea/tests/__init__.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/tests/__init__.py Sat Nov 07 13:24:44 2015 +0100 @@ -220,7 +220,6 @@ user = user and User.get(user) user = user and user.username self.assertEqual(user, expected_username) - self.assertEqual(cookie.get('is_authenticated'), True) def authentication_token(self): return self.app.get(url('authentication_token')).body
--- a/kallithea/tests/models/test_notifications.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/tests/models/test_notifications.py Sat Nov 07 13:24:44 2015 +0100 @@ -144,7 +144,7 @@ Session().commit() self.assertEqual(NotificationModel() - .get_unread_cnt_for_user(self.u1), 1) + .get_unread_cnt_for_user(self.u1), 0) self.assertEqual(NotificationModel() .get_unread_cnt_for_user(self.u2), 0) self.assertEqual(NotificationModel() @@ -156,7 +156,7 @@ Session().commit() self.assertEqual(NotificationModel() - .get_unread_cnt_for_user(self.u1), 2) + .get_unread_cnt_for_user(self.u1), 0) self.assertEqual(NotificationModel() .get_unread_cnt_for_user(self.u2), 1) self.assertEqual(NotificationModel()
--- a/kallithea/tests/other/manual_test_vcs_operations.py Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/tests/other/manual_test_vcs_operations.py Sat Nov 07 13:24:44 2015 +0100 @@ -273,8 +273,8 @@ stdout, stderr = _add_files_and_push('hg', DEST, files_no=1) key = CacheInvalidation.query().filter(CacheInvalidation.cache_key - ==HG_REPO).one() - self.assertEqual(key.cache_active, False) + ==HG_REPO).all() + self.assertEqual(key, []) def test_push_invalidates_cache_git(self): key = CacheInvalidation.query().filter(CacheInvalidation.cache_key @@ -295,9 +295,8 @@ _check_proper_git_push(stdout, stderr) key = CacheInvalidation.query().filter(CacheInvalidation.cache_key - ==GIT_REPO).one() - print CacheInvalidation.get_all() - self.assertEqual(key.cache_active, False) + ==GIT_REPO).all() + self.assertEqual(key, []) def test_push_wrong_credentials_hg(self): DEST = _get_tmp_dir()
--- a/kallithea/tests/test.ini Sat Nov 07 13:16:58 2015 +0100 +++ b/kallithea/tests/test.ini Sat Nov 07 13:24:44 2015 +0100 @@ -163,6 +163,7 @@ ## COMMON ## host = 127.0.0.1 +#port = 5000 port = 4999 ## middleware for hosting the WSGI application under a URL prefix @@ -224,6 +225,7 @@ ## options for showing and identifying changesets show_sha_length = 12 +#show_revision_number = false show_revision_number = true ## gist URL alias, used to create nicer urls for gist. This should be an @@ -280,12 +282,6 @@ #issue_server_link_wiki = https://wiki.example.com/{id} #issue_prefix_wiki = WIKI- -## instance-id prefix -## a prefix key for this instance used for cache invalidation when running -## multiple instances of kallithea, make sure it's globally unique for -## all running kallithea instances. Leave empty if you don't use it -instance_id = - ## alternative return HTTP header for failed authentication. Default HTTP ## response is 401 HTTPUnauthorized. Currently Mercurial clients have trouble with ## handling that. Set this variable to 403 to return HTTPForbidden @@ -301,6 +297,18 @@ ## allows to setup custom hooks in settings page allow_custom_hooks_settings = True +## extra extensions for indexing, space separated and without the leading '.'. +# index.extensions = +# gemfile +# lock + +## extra filenames for indexing, space separated +# index.filenames = +# .dockerignore +# .editorconfig +# INSTALL +# CHANGELOG + #################################### ### CELERY CONFIG #### #################################### @@ -347,6 +355,7 @@ beaker.cache.long_term.key_length = 256 beaker.cache.sql_cache_short.type = memory +#beaker.cache.sql_cache_short.expire = 10 beaker.cache.sql_cache_short.expire = 1 beaker.cache.sql_cache_short.key_length = 256 @@ -543,8 +552,8 @@ [logger_sqlalchemy] #level = INFO +level = ERROR #handlers = console_sql -level = ERROR handlers = console qualname = sqlalchemy.engine propagate = 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/docs-headings.py Sat Nov 07 13:24:44 2015 +0100 @@ -0,0 +1,66 @@ +#!/usr/bin/env python2 + +""" +Consistent formatting of rst section titles +""" + +import re +import subprocess + +spaces = [ + (0, 1), # we assume this is a over-and-underlined header + (2, 1), + (1, 1), + (1, 0), + (1, 0), + ] + +# match on a header line underlined with one of the valid characters +headermatch = re.compile(r'''\n*(.+)\n([][!"#$%&'()*+,./:;<=>?@\\^_`{|}~-])\2{2,}\n+''', flags=re.MULTILINE) + + +def main(): + for fn in subprocess.check_output(['hg', 'loc', 'set:**.rst+kallithea/i18n/how_to']).splitlines(): + print 'processing %s:' % fn + s = file(fn).read() + + # find levels and their styles + lastpos = 0 + styles = [] + for markup in headermatch.findall(s): + style = markup[1] + if style in styles: + stylepos = styles.index(style) + if stylepos > lastpos + 1: + print 'bad style %r with level %s - was at %s' % (style, stylepos, lastpos) + else: + stylepos = len(styles) + if stylepos > lastpos + 1: + print 'bad new style %r - expected %r' % (style, styles[lastpos + 1]) + else: + styles.append(style) + lastpos = stylepos + + # remove superfluous spacing (may however be restored by header spacing) + s = re.sub(r'''(\n\n)\n*''', r'\1', s, flags=re.MULTILINE) + + # rewrite header markup with correct style, length and spacing + def subf(m): + title, style = m.groups() + level = styles.index(style) + before, after = spaces[level] + return '\n' * (before + 1) + title + '\n' + style * len(title) + '\n' * (after + 1) + s = headermatch.sub(subf, s) + + # remove superfluous spacing when headers are adjacent + s = re.sub(r'''(\n.+\n([][!"#$%&'()*+,./:;<=>?@\\^_`{|}~-])\2{2,}\n\n\n)\n*''', r'\1', s, flags=re.MULTILINE) + # fix trailing space and spacing before link sections + s = s.strip() + '\n' + s = re.sub(r'''\n+((?:\.\. _[^\n]*\n)+)$''', r'\n\n\n\1', s) + + file(fn, 'w').write(s) + print subprocess.check_output(['hg', 'diff', fn]) + print + +if __name__ == '__main__': + main()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/generate-ini.py Sat Nov 07 13:24:44 2015 +0100 @@ -0,0 +1,173 @@ +#!/usr/bin/env python2 +""" +Based on kallithea/bin/template.ini.mako, generate + kallithea/config/deployment.ini_tmpl + development.ini + kallithea/tests/test.ini +""" + +import re + +makofile = 'kallithea/bin/template.ini.mako' + +# the mako conditionals used in all other ini files and templates +selected_mako_conditionals = set([ + "database_engine == 'sqlite'", + "http_server == 'waitress'", + "error_aggregation_service == 'errormator'", + "error_aggregation_service == 'sentry'", +]) + +# the mako variables used in all other ini files and templates +mako_variable_values = { + 'host': '127.0.0.1', + 'port': '5000', + 'here': '%(here)s', + 'uuid()': '${app_instance_uuid}', +} + +# files to be generated from the mako template +ini_files = [ + ('kallithea/config/deployment.ini_tmpl', + ''' + Kallithea - Example config + + The %(here)s variable will be replaced with the parent directory of this file + ''', + {}, # exactly the same settings as template.ini.mako + ), + ('kallithea/tests/test.ini', + ''' + Kallithea - config for tests: + initial_repo_scan = true + vcs_full_cache = false + sqlalchemy and kallithea_test.sqlite + custom logging + + The %(here)s variable will be replaced with the parent directory of this file + ''', + { + '[server:main]': { + 'port': '4999', + }, + '[app:main]': { + 'initial_repo_scan': 'true', + 'app_instance_uuid': 'test', + 'vcs_full_cache': 'false', + 'show_revision_number': 'true', + 'beaker.cache.sql_cache_short.expire': '1', + 'beaker.session.secret': '{74e0cd75-b339-478b-b129-07dd221def1f}', + 'sqlalchemy.db1.url': 'sqlite:///%(here)s/kallithea_test.sqlite', + }, + '[logger_root]': { + 'level': 'DEBUG', + }, + '[logger_sqlalchemy]': { + 'level': 'ERROR', + 'handlers': 'console', + }, + '[handler_console]': { + 'level': 'NOTSET', + }, + }, + ), + ('development.ini', + ''' + Kallithea - Development config: + listening on *:5000 + sqlite and kallithea.db + initial_repo_scan = true + set debug = true + verbose and colorful logging + + The %(here)s variable will be replaced with the parent directory of this file + ''', + { + '[server:main]': { + 'host': '0.0.0.0', + }, + '[app:main]': { + 'initial_repo_scan': 'true', + 'set debug': 'true', + 'app_instance_uuid': 'development-not-secret', + 'beaker.session.secret': 'development-not-secret', + }, + '[handler_console]': { + 'level': 'DEBUG', + 'formatter': 'color_formatter', + }, + '[handler_console_sql]': { + 'level': 'DEBUG', + 'formatter': 'color_formatter_sql', + }, + }, + ), +] + + +def main(): + # make sure all mako lines starting with '#' (the '##' comments) are marked up as <text> + print 'reading:', makofile + mako_org = file(makofile).read() + mako_no_text_markup = re.sub(r'</?%text>', '', mako_org) + mako_marked_up = re.sub(r'\n(##.*)', r'\n<%text>\1</%text>', mako_no_text_markup, flags=re.MULTILINE) + if mako_marked_up != mako_org: + print 'writing:', makofile + file(makofile, 'w').write(mako_marked_up) + + # select the right mako conditionals for the other less sophisticated formats + def sub_conditionals(m): + """given a %if...%endif match, replace with just the selected + conditional sections enabled and the rest as comments + """ + conditional_lines = m.group(1) + def sub_conditional(m): + """given a conditional and the corresponding lines, return them raw + or commented out, based on whether conditional is selected + """ + criteria, lines = m.groups() + if criteria not in selected_mako_conditionals: + lines = '\n'.join((l if not l or l.startswith('#') else '#' + l) for l in lines.split('\n')) + return lines + conditional_lines = re.sub(r'^%(?:el)?if (.*):\n((?:^[^%\n].*\n|\n)*)', + sub_conditional, conditional_lines, flags=re.MULTILINE) + return conditional_lines + mako_no_conditionals = re.sub(r'^(%if .*\n(?:[^%\n].*\n|%elif .*\n|\n)*)%endif\n', + sub_conditionals, mako_no_text_markup, flags=re.MULTILINE) + + # expand mako variables + def pyrepl(m): + return mako_variable_values.get(m.group(1), m.group(0)) + mako_no_variables = re.sub(r'\${([^}]*)}', pyrepl, mako_no_conditionals) + + # remove utf-8 coding header + base_ini = re.sub(r'^## -\*- coding: utf-8 -\*-\n', '', mako_no_variables) + + # create ini files + for fn, desc, settings in ini_files: + print 'updating:', fn + ini_lines = re.sub( + '# Kallithea - config file generated with kallithea-config *#\n', + ''.join('# %-77s#\n' % l.strip() for l in desc.strip().split('\n')), + base_ini) + def process_section(m): + """process a ini section, replacing values as necessary""" + sectionname, lines = m.groups() + if sectionname in settings: + section_settings = settings[sectionname] + def process_line(m): + """process a section line and update value if necessary""" + setting, value = m.groups() + line = m.group(0) + if setting in section_settings: + line = '%s = %s' % (setting, section_settings[setting]) + if '$' not in value: + line = '#%s = %s\n%s' % (setting, value, line) + return line.rstrip() + lines = re.sub(r'^([^#\n].*) = ?(.*)', process_line, lines, flags=re.MULTILINE) + return sectionname + '\n' + lines + ini_lines = re.sub(r'^(\[.*\])\n((?:(?:[^[\n].*)?\n)*)', process_section, ini_lines, flags=re.MULTILINE) + file(fn, 'w').write(ini_lines) + +if __name__ == '__main__': + main()