diff --git a/rhodecode/apps/_base/__init__.py b/rhodecode/apps/_base/__init__.py --- a/rhodecode/apps/_base/__init__.py +++ b/rhodecode/apps/_base/__init__.py @@ -362,14 +362,22 @@ class RepoRoutePredicate(object): repo_model = repo.RepoModel() by_name_match = repo_model.get_by_repo_name(repo_name, cache=True) + def redirect_if_creating(db_repo): + if db_repo.repo_state in [repo.Repository.STATE_PENDING]: + raise HTTPFound( + request.route_path('repo_creating', + repo_name=db_repo.repo_name)) + if by_name_match: # register this as request object we can re-use later request.db_repo = by_name_match + redirect_if_creating(by_name_match) return True by_id_match = repo_model.get_repo_by_id(repo_name) if by_id_match: request.db_repo = by_id_match + redirect_if_creating(by_id_match) return True return False diff --git a/rhodecode/apps/repository/__init__.py b/rhodecode/apps/repository/__init__.py --- a/rhodecode/apps/repository/__init__.py +++ b/rhodecode/apps/repository/__init__.py @@ -22,6 +22,15 @@ from rhodecode.apps._base import add_rou def includeme(config): + # repo creating checks, special cases that aren't repo routes + config.add_route( + name='repo_creating', + pattern='/{repo_name:.*?[^/]}/repo_creating') + + config.add_route( + name='repo_creating_check', + pattern='/{repo_name:.*?[^/]}/repo_creating_check') + # Summary # NOTE(marcink): one additional route is defined in very bottom, catch # all pattern diff --git a/rhodecode/apps/repository/tests/test_repo_summary.py b/rhodecode/apps/repository/tests/test_repo_summary.py --- a/rhodecode/apps/repository/tests/test_repo_summary.py +++ b/rhodecode/apps/repository/tests/test_repo_summary.py @@ -47,8 +47,8 @@ def route_path(name, params=None, **kwar 'repo_summary': '/{repo_name}', 'repo_stats': '/{repo_name}/repo_stats/{commit_id}', 'repo_refs_data': '/{repo_name}/refs-data', - 'repo_refs_changelog_data': '/{repo_name}/refs-data-changelog' - + 'repo_refs_changelog_data': '/{repo_name}/refs-data-changelog', + 'repo_creating_check': '/{repo_name}/repo_creating_check', }[name].format(**kwargs) if params: diff --git a/rhodecode/apps/repository/views/repo_checks.py b/rhodecode/apps/repository/views/repo_checks.py new file mode 100644 --- /dev/null +++ b/rhodecode/apps/repository/views/repo_checks.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2011-2017 RhodeCode GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License, version 3 +# (only), as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# This program is dual-licensed. If you wish to learn more about the +# RhodeCode Enterprise Edition, including its added features, Support services, +# and proprietary license terms, please see https://rhodecode.com/licenses/ + +import logging + +from pyramid.view import view_config +from pyramid.httpexceptions import HTTPFound, HTTPNotFound + +from rhodecode.apps._base import BaseAppView +from rhodecode.lib import helpers as h +from rhodecode.lib.auth import (NotAnonymous, HasRepoPermissionAny) +from rhodecode.model.db import Repository + +log = logging.getLogger(__name__) + + +class RepoChecksView(BaseAppView): + def load_default_context(self): + c = self._get_local_tmpl_context() + self._register_global_c(c) + return c + + @NotAnonymous() + @view_config( + route_name='repo_creating', request_method='GET', + renderer='rhodecode:templates/admin/repos/repo_creating.mako') + def repo_creating(self): + c = self.load_default_context() + + repo_name = self.request.matchdict['repo_name'] + db_repo = Repository.get_by_repo_name(repo_name) + if not db_repo: + raise HTTPNotFound() + + # check if maybe repo is already created + if db_repo.repo_state in [Repository.STATE_CREATED]: + # re-check permissions before redirecting to prevent resource + # discovery by checking the 302 code + perm_set = ['repository.read', 'repository.write', 'repository.admin'] + has_perm = HasRepoPermissionAny(*perm_set)( + db_repo.repo_name, 'Repo Creating check') + if not has_perm: + raise HTTPNotFound() + + raise HTTPFound(h.route_path( + 'repo_summary', repo_name=db_repo.repo_name)) + + c.task_id = self.request.GET.get('task_id') + c.repo_name = repo_name + + return self._get_template_context(c) + + @NotAnonymous() + @view_config( + route_name='repo_creating_check', request_method='GET', + renderer='json_ext') + def repo_creating_check(self): + _ = self.request.translate + task_id = self.request.GET.get('task_id') + self.load_default_context() + + repo_name = self.request.matchdict['repo_name'] + + if task_id and task_id not in ['None']: + import rhodecode + from celery.result import AsyncResult + if rhodecode.CELERY_ENABLED: + task = AsyncResult(task_id) + if task.failed(): + msg = self._log_creation_exception(task.result, repo_name) + h.flash(msg, category='error') + raise HTTPFound(h.route_path('home'), code=501) + + db_repo = Repository.get_by_repo_name(repo_name) + if db_repo and db_repo.repo_state == Repository.STATE_CREATED: + if db_repo.clone_uri: + clone_uri = db_repo.clone_uri_hidden + h.flash(_('Created repository %s from %s') + % (db_repo.repo_name, clone_uri), category='success') + else: + repo_url = h.link_to( + db_repo.repo_name, + h.route_path('repo_summary', repo_name=db_repo.repo_name)) + fork = db_repo.fork + if fork: + fork_name = fork.repo_name + h.flash(h.literal(_('Forked repository %s as %s') + % (fork_name, repo_url)), category='success') + else: + h.flash(h.literal(_('Created repository %s') % repo_url), + category='success') + return {'result': True} + return {'result': False} diff --git a/rhodecode/config/routing.py b/rhodecode/config/routing.py --- a/rhodecode/config/routing.py +++ b/rhodecode/config/routing.py @@ -416,13 +416,6 @@ def make_map(config): # REPOSITORY ROUTES #========================================================================== - rmap.connect('repo_creating_home', '/{repo_name}/repo_creating', - controller='admin/repos', action='repo_creating', - requirements=URL_NAME_REQUIREMENTS) - rmap.connect('repo_check_home', '/{repo_name}/crepo_check', - controller='admin/repos', action='repo_check', - requirements=URL_NAME_REQUIREMENTS) - # repo edit options rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields', controller='admin/repos', action='edit_fields', diff --git a/rhodecode/controllers/admin/repos.py b/rhodecode/controllers/admin/repos.py --- a/rhodecode/controllers/admin/repos.py +++ b/rhodecode/controllers/admin/repos.py @@ -31,8 +31,9 @@ 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 HTTPForbidden, HTTPNotFound, HTTPBadRequest +from webob.exc import HTTPForbidden, HTTPBadRequest +from pyramid.httpexceptions import HTTPFound import rhodecode from rhodecode.lib import auth, helpers as h from rhodecode.lib.auth import ( @@ -183,9 +184,10 @@ class ReposController(BaseRepoController h.flash(msg, category='error') return redirect(h.route_path('home')) - return redirect(h.url('repo_creating_home', - repo_name=form_result['repo_name_full'], - task_id=task_id)) + raise HTTPFound( + h.route_path('repo_creating', + repo_name=form_result['repo_name_full'], + _query=dict(task_id=task_id))) # perms check inside @NotAnonymous() @@ -239,51 +241,6 @@ class ReposController(BaseRepoController force_defaults=False ) - @NotAnonymous() - def repo_creating(self, repo_name): - c.repo = repo_name - c.task_id = request.GET.get('task_id') - if not c.repo: - raise HTTPNotFound() - return render('admin/repos/repo_creating.mako') - - @NotAnonymous() - @jsonify - def repo_check(self, repo_name): - c.repo = repo_name - task_id = request.GET.get('task_id') - - if task_id and task_id not in ['None']: - import rhodecode - from celery.result import AsyncResult - if rhodecode.CELERY_ENABLED: - task = AsyncResult(task_id) - if task.failed(): - msg = self._log_creation_exception(task.result, c.repo) - h.flash(msg, category='error') - return redirect(h.route_path('home'), code=501) - - repo = Repository.get_by_repo_name(repo_name) - if repo and repo.repo_state == Repository.STATE_CREATED: - if repo.clone_uri: - clone_uri = repo.clone_uri_hidden - h.flash(_('Created repository %s from %s') - % (repo.repo_name, clone_uri), category='success') - else: - repo_url = h.link_to( - repo.repo_name, - h.route_path('repo_summary',repo_name=repo.repo_name)) - fork = repo.fork - if fork: - fork_name = fork.repo_name - h.flash(h.literal(_('Forked repository %s as %s') - % (fork_name, repo_url)), category='success') - else: - h.flash(h.literal(_('Created repository %s') % repo_url), - category='success') - return {'result': True} - return {'result': False} - @HasPermissionAllDecorator('hg.admin') def show(self, repo_name, format='html'): """GET /repos/repo_name: Show a specific item""" diff --git a/rhodecode/controllers/forks.py b/rhodecode/controllers/forks.py --- a/rhodecode/controllers/forks.py +++ b/rhodecode/controllers/forks.py @@ -30,6 +30,7 @@ from pylons import tmpl_context as c, re from pylons.controllers.util import redirect from pylons.i18n.translation import _ +from pyramid.httpexceptions import HTTPFound import rhodecode.lib.helpers as h from rhodecode.lib import auth @@ -191,6 +192,7 @@ class ForksController(BaseRepoController (repo_name, )) h.flash(msg, category='error') - return redirect(h.url('repo_creating_home', - repo_name=form_result['repo_name_full'], - task_id=task_id)) + raise HTTPFound( + h.route_path('repo_creating', + repo_name=form_result['repo_name_full'], + _query=dict(task_id=task_id))) diff --git a/rhodecode/public/css/main.less b/rhodecode/public/css/main.less --- a/rhodecode/public/css/main.less +++ b/rhodecode/public/css/main.less @@ -1833,6 +1833,11 @@ BIN_FILENODE = 7 } } + +.creation_in_progress { + color: @grey4 +} + .status_box_menu { margin: 0; } diff --git a/rhodecode/public/js/rhodecode/routes.js b/rhodecode/public/js/rhodecode/routes.js --- a/rhodecode/public/js/rhodecode/routes.js +++ b/rhodecode/public/js/rhodecode/routes.js @@ -96,6 +96,8 @@ function registerRCRoutes() { pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []); pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []); pyroutes.register('toggle_following', '/_admin/toggle_following', []); + pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']); + pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']); pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']); pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']); pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']); diff --git a/rhodecode/templates/admin/repos/repo_creating.mako b/rhodecode/templates/admin/repos/repo_creating.mako --- a/rhodecode/templates/admin/repos/repo_creating.mako +++ b/rhodecode/templates/admin/repos/repo_creating.mako @@ -2,14 +2,14 @@ <%inherit file="/base/base.mako"/> <%def name="title()"> - ${_('%s Creating repository') % c.repo_name} + ${_('{} Creating repository').format(c.repo_name)} %if c.rhodecode_name: · ${h.branding(c.rhodecode_name)} %endif <%def name="breadcrumbs_links()"> - ${_('Creating repository')} ${c.repo} + ${_('Creating repository')} ${c.repo_name} <%def name="menu_bar_nav()"> @@ -38,7 +38,7 @@ - \ No newline at end of file + diff --git a/rhodecode/templates/data_table/_dt_elements.mako b/rhodecode/templates/data_table/_dt_elements.mako --- a/rhodecode/templates/data_table/_dt_elements.mako +++ b/rhodecode/templates/data_table/_dt_elements.mako @@ -67,7 +67,9 @@ %endif %if rstate == 'repo_state_pending': - + + (${_('creating...')}) + %endif diff --git a/rhodecode/tests/functional/test_admin_repo_groups.py b/rhodecode/tests/functional/test_admin_repo_groups.py --- a/rhodecode/tests/functional/test_admin_repo_groups.py +++ b/rhodecode/tests/functional/test_admin_repo_groups.py @@ -122,8 +122,6 @@ class _BaseTest(TestController): csrf_token=self.csrf_token)) # run the check page that triggers the flash message - # response = self.app.get(url('repo_check_home', repo_name=repo_name)) - # assert response.json == {u'result': True} repo_gr_url = h.route_path( 'repo_group_home', repo_group_name=repo_group_name) diff --git a/rhodecode/tests/functional/test_admin_repos.py b/rhodecode/tests/functional/test_admin_repos.py --- a/rhodecode/tests/functional/test_admin_repos.py +++ b/rhodecode/tests/functional/test_admin_repos.py @@ -35,14 +35,26 @@ from rhodecode.model.settings import Set from rhodecode.model.user import UserModel from rhodecode.tests import ( login_user_session, url, assert_session_flash, TEST_USER_ADMIN_LOGIN, - TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, HG_REPO, GIT_REPO, - logout_user_session) + TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, logout_user_session) from rhodecode.tests.fixture import Fixture, error_function from rhodecode.tests.utils import AssertResponse, repo_on_filesystem fixture = Fixture() +def route_path(name, params=None, **kwargs): + import urllib + + base_url = { + 'repo_summary': '/{repo_name}', + 'repo_creating_check': '/{repo_name}/repo_creating_check', + }[name].format(**kwargs) + + if params: + base_url = '{}?{}'.format(base_url, urllib.urlencode(params)) + return base_url + + @pytest.mark.usefixtures("app") class TestAdminRepos(object): @@ -461,7 +473,8 @@ class TestAdminRepos(object): repo_name_utf8 = safe_str(repo_name) # run the check page that triggers the flash message - response = self.app.get(url('repo_check_home', repo_name=repo_name)) + response = self.app.get( + route_path('repo_creating_check', repo_name=safe_str(repo_name))) assert response.json == {u'result': True} flash_msg = u'Created repository {}'.format( @@ -475,7 +488,8 @@ class TestAdminRepos(object): assert new_repo.description == description # test if the repository is visible in the list ? - response = self.app.get(h.route_path('repo_summary', repo_name=repo_name)) + response = self.app.get( + h.route_path('repo_summary', repo_name=safe_str(repo_name))) response.mustcontain(repo_name) response.mustcontain(backend.alias) diff --git a/rhodecode/tests/functional/test_forks.py b/rhodecode/tests/functional/test_forks.py --- a/rhodecode/tests/functional/test_forks.py +++ b/rhodecode/tests/functional/test_forks.py @@ -32,6 +32,19 @@ from rhodecode.model.meta import Session fixture = Fixture() +def route_path(name, params=None, **kwargs): + import urllib + + base_url = { + 'repo_summary': '/{repo_name}', + 'repo_creating_check': '/{repo_name}/repo_creating_check', + }[name].format(**kwargs) + + if params: + base_url = '{}?{}'.format(base_url, urllib.urlencode(params)) + return base_url + + class _BaseTest(TestController): REPO = None @@ -134,7 +147,8 @@ class _BaseTest(TestController): assert repo.fork.repo_name == self.REPO # run the check page that triggers the flash message - response = self.app.get(url('repo_check_home', repo_name=fork_name_full)) + response = self.app.get( + route_path('repo_creating_check', repo_name=fork_name_full)) # test if we have a message that fork is ok assert_session_flash(response, 'Forked repository %s as %s' @@ -180,7 +194,8 @@ class _BaseTest(TestController): assert repo.fork.repo_name == self.REPO # run the check page that triggers the flash message - response = self.app.get(url('repo_check_home', repo_name=fork_name)) + response = self.app.get( + route_path('repo_creating_check', repo_name=fork_name)) # test if we have a message that fork is ok assert_session_flash(response, 'Forked repository %s as %s'