forks: moved pylons code into pyramid.

This commit is contained in:
Marcin Kuzminski 2017-08-01 11:48:16 +02:00
parent 81eb5a6b72
commit 05386904e7
15 changed files with 683 additions and 609 deletions

View file

@ -219,10 +219,31 @@ def includeme(config):
name='branches_home',
pattern='/{repo_name:.*?[^/]}/branches', repo_route=True)
# Bookmarks
config.add_route(
name='bookmarks_home',
pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True)
# Forks
config.add_route(
name='repo_fork_new',
pattern='/{repo_name:.*?[^/]}/fork', repo_route=True,
repo_accepted_types=['hg', 'git'])
config.add_route(
name='repo_fork_create',
pattern='/{repo_name:.*?[^/]}/fork/create', repo_route=True,
repo_accepted_types=['hg', 'git'])
config.add_route(
name='repo_forks_show_all',
pattern='/{repo_name:.*?[^/]}/forks', repo_route=True,
repo_accepted_types=['hg', 'git'])
config.add_route(
name='repo_forks_data',
pattern='/{repo_name:.*?[^/]}/forks/data', repo_route=True,
repo_accepted_types=['hg', 'git'])
# Pull Requests
config.add_route(
name='pullrequest_show',

View file

@ -0,0 +1,315 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2010-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 <http://www.gnu.org/licenses/>.
#
# 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 pytest
from rhodecode.tests import TestController, assert_session_flash, HG_FORK, GIT_FORK
from rhodecode.tests.fixture import Fixture
from rhodecode.lib import helpers as h
from rhodecode.model.db import Repository
from rhodecode.model.repo import RepoModel
from rhodecode.model.user import UserModel
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',
'repo_fork_new': '/{repo_name}/fork',
'repo_fork_create': '/{repo_name}/fork/create',
'repo_forks_show_all': '/{repo_name}/forks',
'repo_forks_data': '/{repo_name}/forks/data',
}[name].format(**kwargs)
if params:
base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
return base_url
FORK_NAME = {
'hg': HG_FORK,
'git': GIT_FORK
}
@pytest.mark.skip_backends('svn')
class TestRepoForkViewTests(TestController):
def test_show_forks(self, backend, xhr_header):
self.log_user()
response = self.app.get(
route_path('repo_forks_data', repo_name=backend.repo_name),
extra_environ=xhr_header)
assert response.json == {u'data': [], u'draw': None,
u'recordsFiltered': 0, u'recordsTotal': 0}
def test_no_permissions_to_fork_page(self, backend, user_util):
user = user_util.create_user(password='qweqwe')
user_id = user.user_id
self.log_user(user.username, 'qweqwe')
user_model = UserModel()
user_model.revoke_perm(user_id, 'hg.fork.repository')
user_model.grant_perm(user_id, 'hg.fork.none')
u = UserModel().get(user_id)
u.inherit_default_permissions = False
Session().commit()
# try create a fork
self.app.get(
route_path('repo_fork_new', repo_name=backend.repo_name),
status=404)
def test_no_permissions_to_fork_submit(self, backend, csrf_token, user_util):
user = user_util.create_user(password='qweqwe')
user_id = user.user_id
self.log_user(user.username, 'qweqwe')
user_model = UserModel()
user_model.revoke_perm(user_id, 'hg.fork.repository')
user_model.grant_perm(user_id, 'hg.fork.none')
u = UserModel().get(user_id)
u.inherit_default_permissions = False
Session().commit()
# try create a fork
self.app.post(
route_path('repo_fork_create', repo_name=backend.repo_name),
{'csrf_token': csrf_token},
status=404)
def test_fork_missing_data(self, autologin_user, backend, csrf_token):
# try create a fork
response = self.app.post(
route_path('repo_fork_create', repo_name=backend.repo_name),
{'csrf_token': csrf_token},
status=200)
# test if html fill works fine
response.mustcontain('Missing value')
def test_create_fork_page(self, autologin_user, backend):
self.app.get(
route_path('repo_fork_new', repo_name=backend.repo_name),
status=200)
def test_create_and_show_fork(
self, autologin_user, backend, csrf_token, xhr_header):
# create a fork
fork_name = FORK_NAME[backend.alias]
description = 'fork of vcs test'
repo_name = backend.repo_name
source_repo = Repository.get_by_repo_name(repo_name)
creation_args = {
'repo_name': fork_name,
'repo_group': '',
'fork_parent_id': source_repo.repo_id,
'repo_type': backend.alias,
'description': description,
'private': 'False',
'landing_rev': 'rev:tip',
'csrf_token': csrf_token,
}
self.app.post(
route_path('repo_fork_create', repo_name=repo_name), creation_args)
response = self.app.get(
route_path('repo_forks_data', repo_name=repo_name),
extra_environ=xhr_header)
assert response.json['data'][0]['fork_name'] == \
"""<a href="/%s">%s</a>""" % (fork_name, fork_name)
# remove this fork
fixture.destroy_repo(fork_name)
def test_fork_create(self, autologin_user, backend, csrf_token):
fork_name = FORK_NAME[backend.alias]
description = 'fork of vcs test'
repo_name = backend.repo_name
source_repo = Repository.get_by_repo_name(repo_name)
creation_args = {
'repo_name': fork_name,
'repo_group': '',
'fork_parent_id': source_repo.repo_id,
'repo_type': backend.alias,
'description': description,
'private': 'False',
'landing_rev': 'rev:tip',
'csrf_token': csrf_token,
}
self.app.post(
route_path('repo_fork_create', repo_name=repo_name), creation_args)
repo = Repository.get_by_repo_name(FORK_NAME[backend.alias])
assert repo.fork.repo_name == backend.repo_name
# run the check page that triggers the flash message
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 <a href="/%s">%s</a>'
% (repo_name, fork_name, fork_name))
# test if the fork was created in the database
fork_repo = Session().query(Repository)\
.filter(Repository.repo_name == fork_name).one()
assert fork_repo.repo_name == fork_name
assert fork_repo.fork.repo_name == repo_name
# test if the repository is visible in the list ?
response = self.app.get(
h.route_path('repo_summary', repo_name=fork_name))
response.mustcontain(fork_name)
response.mustcontain(backend.alias)
response.mustcontain('Fork of')
response.mustcontain('<a href="/%s">%s</a>' % (repo_name, repo_name))
def test_fork_create_into_group(self, autologin_user, backend, csrf_token):
group = fixture.create_repo_group('vc')
group_id = group.group_id
fork_name = FORK_NAME[backend.alias]
fork_name_full = 'vc/%s' % fork_name
description = 'fork of vcs test'
repo_name = backend.repo_name
source_repo = Repository.get_by_repo_name(repo_name)
creation_args = {
'repo_name': fork_name,
'repo_group': group_id,
'fork_parent_id': source_repo.repo_id,
'repo_type': backend.alias,
'description': description,
'private': 'False',
'landing_rev': 'rev:tip',
'csrf_token': csrf_token,
}
self.app.post(
route_path('repo_fork_create', repo_name=repo_name), creation_args)
repo = Repository.get_by_repo_name(fork_name_full)
assert repo.fork.repo_name == backend.repo_name
# run the check page that triggers the flash message
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 <a href="/%s">%s</a>'
% (repo_name, fork_name_full, fork_name_full))
# test if the fork was created in the database
fork_repo = Session().query(Repository)\
.filter(Repository.repo_name == fork_name_full).one()
assert fork_repo.repo_name == fork_name_full
assert fork_repo.fork.repo_name == repo_name
# test if the repository is visible in the list ?
response = self.app.get(
h.route_path('repo_summary', repo_name=fork_name_full))
response.mustcontain(fork_name_full)
response.mustcontain(backend.alias)
response.mustcontain('Fork of')
response.mustcontain('<a href="/%s">%s</a>' % (repo_name, repo_name))
fixture.destroy_repo(fork_name_full)
fixture.destroy_repo_group(group_id)
def test_fork_read_permission(self, backend, xhr_header, user_util):
user = user_util.create_user(password='qweqwe')
user_id = user.user_id
self.log_user(user.username, 'qweqwe')
# create a fake fork
fork = user_util.create_repo(repo_type=backend.alias)
source = user_util.create_repo(repo_type=backend.alias)
repo_name = source.repo_name
fork.fork_id = source.repo_id
fork_name = fork.repo_name
Session().commit()
forks = Repository.query()\
.filter(Repository.repo_type == backend.alias)\
.filter(Repository.fork_id == source.repo_id).all()
assert 1 == len(forks)
# set read permissions for this
RepoModel().grant_user_permission(
repo=forks[0], user=user_id, perm='repository.read')
Session().commit()
response = self.app.get(
route_path('repo_forks_data', repo_name=repo_name),
extra_environ=xhr_header)
assert response.json['data'][0]['fork_name'] == \
"""<a href="/%s">%s</a>""" % (fork_name, fork_name)
def test_fork_none_permission(self, backend, xhr_header, user_util):
user = user_util.create_user(password='qweqwe')
user_id = user.user_id
self.log_user(user.username, 'qweqwe')
# create a fake fork
fork = user_util.create_repo(repo_type=backend.alias)
source = user_util.create_repo(repo_type=backend.alias)
repo_name = source.repo_name
fork.fork_id = source.repo_id
Session().commit()
forks = Repository.query()\
.filter(Repository.repo_type == backend.alias)\
.filter(Repository.fork_id == source.repo_id).all()
assert 1 == len(forks)
# set none
RepoModel().grant_user_permission(
repo=forks[0], user=user_id, perm='repository.none')
Session().commit()
# fork shouldn't be there
response = self.app.get(
route_path('repo_forks_data', repo_name=repo_name),
extra_environ=xhr_header)
assert response.json == {u'data': [], u'draw': None,
u'recordsFiltered': 0, u'recordsTotal': 0}
class TestSVNFork(TestController):
@pytest.mark.parametrize('route_name', [
'repo_fork_create', 'repo_fork_new'
])
def test_fork_redirects(self, autologin_user, backend_svn, route_name):
self.app.get(route_path(
route_name, repo_name=backend_svn.repo_name),
status=404)

View file

@ -0,0 +1,256 @@
# -*- 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 <http://www.gnu.org/licenses/>.
#
# 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
import datetime
import formencode
from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
from pyramid.view import view_config
from pyramid.renderers import render
from pyramid.response import Response
from rhodecode.apps._base import RepoAppView, DataGridAppView
from rhodecode.lib.auth import (
LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous,
HasRepoPermissionAny, HasPermissionAnyDecorator, CSRFRequired)
import rhodecode.lib.helpers as h
from rhodecode.model.db import (
coalesce, or_, Repository, RepoGroup, UserFollowing, User)
from rhodecode.model.repo import RepoModel
from rhodecode.model.forms import RepoForkForm
from rhodecode.model.scm import ScmModel, RepoGroupList
from rhodecode.lib.utils2 import safe_int, safe_unicode
log = logging.getLogger(__name__)
class RepoForksView(RepoAppView, DataGridAppView):
def load_default_context(self):
c = self._get_local_tmpl_context(include_app_defaults=True)
# TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
c.repo_info = self.db_repo
c.rhodecode_repo = self.rhodecode_vcs_repo
acl_groups = RepoGroupList(
RepoGroup.query().all(),
perm_set=['group.write', 'group.admin'])
c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups)
choices, c.landing_revs = ScmModel().get_repo_landing_revs()
c.landing_revs_choices = choices
c.personal_repo_group = c.rhodecode_user.personal_repo_group
self._register_global_c(c)
return c
@LoginRequired()
@HasRepoPermissionAnyDecorator(
'repository.read', 'repository.write', 'repository.admin')
@view_config(
route_name='repo_forks_show_all', request_method='GET',
renderer='rhodecode:templates/forks/forks.mako')
def repo_forks_show_all(self):
c = self.load_default_context()
return self._get_template_context(c)
@LoginRequired()
@HasRepoPermissionAnyDecorator(
'repository.read', 'repository.write', 'repository.admin')
@view_config(
route_name='repo_forks_data', request_method='GET',
renderer='json_ext', xhr=True)
def repo_forks_data(self):
_ = self.request.translate
column_map = {
'fork_name': 'repo_name',
'fork_date': 'created_on',
'last_activity': 'updated_on'
}
draw, start, limit = self._extract_chunk(self.request)
search_q, order_by, order_dir = self._extract_ordering(
self.request, column_map=column_map)
acl_check = HasRepoPermissionAny(
'repository.read', 'repository.write', 'repository.admin')
repo_id = self.db_repo.repo_id
allowed_ids = []
for f in Repository.query().filter(Repository.fork_id == repo_id):
if acl_check(f.repo_name, 'get forks check'):
allowed_ids.append(f.repo_id)
forks_data_total_count = Repository.query()\
.filter(Repository.fork_id == repo_id)\
.filter(Repository.repo_id.in_(allowed_ids))\
.count()
# json generate
base_q = Repository.query()\
.filter(Repository.fork_id == repo_id)\
.filter(Repository.repo_id.in_(allowed_ids))\
if search_q:
like_expression = u'%{}%'.format(safe_unicode(search_q))
base_q = base_q.filter(or_(
Repository.repo_name.ilike(like_expression),
Repository.description.ilike(like_expression),
))
forks_data_total_filtered_count = base_q.count()
sort_col = getattr(Repository, order_by, None)
if sort_col:
if order_dir == 'asc':
# handle null values properly to order by NULL last
if order_by in ['last_activity']:
sort_col = coalesce(sort_col, datetime.date.max)
sort_col = sort_col.asc()
else:
# handle null values properly to order by NULL last
if order_by in ['last_activity']:
sort_col = coalesce(sort_col, datetime.date.min)
sort_col = sort_col.desc()
base_q = base_q.order_by(sort_col)
base_q = base_q.offset(start).limit(limit)
fork_list = base_q.all()
def fork_actions(fork):
url_link = h.route_path(
'repo_compare',
repo_name=fork.repo_name,
source_ref_type=self.db_repo.landing_rev[0],
source_ref=self.db_repo.landing_rev[1],
target_ref_type=self.db_repo.landing_rev[0],
target_ref=self.db_repo.landing_rev[1],
_query=dict(merge=1, target_repo=f.repo_name))
return h.link_to(_('Compare fork'), url_link, class_='btn-link')
def fork_name(fork):
return h.link_to(fork.repo_name,
h.route_path('repo_summary', repo_name=fork.repo_name))
forks_data = []
for fork in fork_list:
forks_data.append({
"username": h.gravatar_with_user(self.request, fork.user.username),
"fork_name": fork_name(fork),
"description": fork.description,
"fork_date": h.age_component(fork.created_on, time_is_local=True),
"last_activity": h.format_date(fork.updated_on),
"action": fork_actions(fork),
})
data = ({
'draw': draw,
'data': forks_data,
'recordsTotal': forks_data_total_count,
'recordsFiltered': forks_data_total_filtered_count,
})
return data
@LoginRequired()
@NotAnonymous()
@HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
@HasRepoPermissionAnyDecorator(
'repository.read', 'repository.write', 'repository.admin')
@view_config(
route_name='repo_fork_new', request_method='GET',
renderer='rhodecode:templates/forks/forks.mako')
def repo_fork_new(self):
c = self.load_default_context()
defaults = RepoModel()._get_defaults(self.db_repo_name)
# alter the description to indicate a fork
defaults['description'] = (
'fork of repository: %s \n%s' % (
defaults['repo_name'], defaults['description']))
# add suffix to fork
defaults['repo_name'] = '%s-fork' % defaults['repo_name']
data = render('rhodecode:templates/forks/fork.mako',
self._get_template_context(c), self.request)
html = formencode.htmlfill.render(
data,
defaults=defaults,
encoding="UTF-8",
force_defaults=False
)
return Response(html)
@LoginRequired()
@NotAnonymous()
@HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
@HasRepoPermissionAnyDecorator(
'repository.read', 'repository.write', 'repository.admin')
@CSRFRequired()
@view_config(
route_name='repo_fork_create', request_method='POST',
renderer='rhodecode:templates/forks/fork.mako')
def repo_fork_create(self):
_ = self.request.translate
c = self.load_default_context()
_form = RepoForkForm(old_data={'repo_type': self.db_repo.repo_type},
repo_groups=c.repo_groups_choices,
landing_revs=c.landing_revs_choices)()
form_result = {}
task_id = None
try:
form_result = _form.to_python(dict(self.request.POST))
# create fork is done sometimes async on celery, db transaction
# management is handled there.
task = RepoModel().create_fork(
form_result, c.rhodecode_user.user_id)
from celery.result import BaseAsyncResult
if isinstance(task, BaseAsyncResult):
task_id = task.task_id
except formencode.Invalid as errors:
c.repo_info = self.db_repo
data = render('rhodecode:templates/forks/fork.mako',
self._get_template_context(c), self.request)
html = formencode.htmlfill.render(
data,
defaults=errors.value,
errors=errors.error_dict or {},
prefix_error=False,
encoding="UTF-8",
force_defaults=False
)
return Response(html)
except Exception:
log.exception(
u'Exception while trying to fork the repository %s',
self.db_repo_name)
msg = (
_('An error occurred during repository forking %s') % (
self.db_repo_name, ))
h.flash(msg, category='error')
repo_name = form_result.get('repo_name_full', self.db_repo_name)
raise HTTPFound(
h.route_path('repo_creating',
repo_name=repo_name,
_query=dict(task_id=task_id)))

View file

@ -489,20 +489,4 @@ def make_map(config):
conditions={'method': ['GET', 'POST'], 'function': check_repo},
requirements=URL_NAME_REQUIREMENTS)
rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
controller='forks', action='fork_create',
conditions={'function': check_repo, 'method': ['POST']},
requirements=URL_NAME_REQUIREMENTS)
rmap.connect('repo_fork_home', '/{repo_name}/fork',
controller='forks', action='fork',
conditions={'function': check_repo},
requirements=URL_NAME_REQUIREMENTS)
rmap.connect('repo_forks_home', '/{repo_name}/forks',
controller='forks', action='forks',
conditions={'function': check_repo},
requirements=URL_NAME_REQUIREMENTS)
return rmap

View file

@ -1,198 +0,0 @@
# -*- 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 <http://www.gnu.org/licenses/>.
#
# 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/
"""
forks controller for rhodecode
"""
import formencode
import logging
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 pyramid.httpexceptions import HTTPFound
import rhodecode.lib.helpers as h
from rhodecode.lib import auth
from rhodecode.lib.helpers import Page
from rhodecode.lib.auth import (
LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous,
HasRepoPermissionAny, HasPermissionAnyDecorator, HasAcceptedRepoType)
from rhodecode.lib.base import BaseRepoController, render
from rhodecode.model.db import Repository, RepoGroup, UserFollowing, User
from rhodecode.model.repo import RepoModel
from rhodecode.model.forms import RepoForkForm
from rhodecode.model.scm import ScmModel, RepoGroupList
from rhodecode.lib.utils2 import safe_int
log = logging.getLogger(__name__)
class ForksController(BaseRepoController):
def __before__(self):
super(ForksController, self).__before__()
def __load_defaults(self):
acl_groups = RepoGroupList(
RepoGroup.query().all(),
perm_set=['group.write', 'group.admin'])
c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
choices, c.landing_revs = ScmModel().get_repo_landing_revs()
c.landing_revs_choices = choices
c.personal_repo_group = c.rhodecode_user.personal_repo_group
def __load_data(self, repo_name=None):
"""
Load defaults settings for edit, and update
:param repo_name:
"""
self.__load_defaults()
c.repo_info = Repository.get_by_repo_name(repo_name)
repo = c.repo_info.scm_instance()
if c.repo_info is None:
h.not_mapped_error(repo_name)
return redirect(url('repos'))
c.default_user_id = User.get_default_user().user_id
c.in_public_journal = UserFollowing.query()\
.filter(UserFollowing.user_id == c.default_user_id)\
.filter(UserFollowing.follows_repository == c.repo_info).scalar()
if c.repo_info.stats:
last_rev = c.repo_info.stats.stat_on_revision+1
else:
last_rev = 0
c.stats_revision = last_rev
c.repo_last_rev = repo.count()
if last_rev == 0 or c.repo_last_rev == 0:
c.stats_percentage = 0
else:
c.stats_percentage = '%.2f' % ((float((last_rev)) /
c.repo_last_rev) * 100)
defaults = RepoModel()._get_defaults(repo_name)
# alter the description to indicate a fork
defaults['description'] = ('fork of repository: %s \n%s'
% (defaults['repo_name'],
defaults['description']))
# add suffix to fork
defaults['repo_name'] = '%s-fork' % defaults['repo_name']
return defaults
@LoginRequired()
@HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
'repository.admin')
@HasAcceptedRepoType('git', 'hg')
def forks(self, repo_name):
p = safe_int(request.GET.get('page', 1), 1)
repo_id = c.rhodecode_db_repo.repo_id
d = []
for r in Repository.get_repo_forks(repo_id):
if not HasRepoPermissionAny(
'repository.read', 'repository.write', 'repository.admin'
)(r.repo_name, 'get forks check'):
continue
d.append(r)
c.forks_pager = Page(d, page=p, items_per_page=20)
c.forks_data = render('/forks/forks_data.mako')
if request.environ.get('HTTP_X_PJAX'):
return c.forks_data
return render('/forks/forks.mako')
@LoginRequired()
@NotAnonymous()
@HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
@HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
'repository.admin')
@HasAcceptedRepoType('git', 'hg')
def fork(self, repo_name):
c.repo_info = Repository.get_by_repo_name(repo_name)
if not c.repo_info:
h.not_mapped_error(repo_name)
return redirect(h.route_path('home'))
defaults = self.__load_data(repo_name)
return htmlfill.render(
render('forks/fork.mako'),
defaults=defaults,
encoding="UTF-8",
force_defaults=False
)
@LoginRequired()
@NotAnonymous()
@HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
@HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
'repository.admin')
@HasAcceptedRepoType('git', 'hg')
@auth.CSRFRequired()
def fork_create(self, repo_name):
self.__load_defaults()
c.repo_info = Repository.get_by_repo_name(repo_name)
_form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type},
repo_groups=c.repo_groups_choices,
landing_revs=c.landing_revs_choices)()
form_result = {}
task_id = None
try:
form_result = _form.to_python(dict(request.POST))
# create fork is done sometimes async on celery, db transaction
# management is handled there.
task = RepoModel().create_fork(
form_result, c.rhodecode_user.user_id)
from celery.result import BaseAsyncResult
if isinstance(task, BaseAsyncResult):
task_id = task.task_id
except formencode.Invalid as errors:
c.new_repo = errors.value['repo_name']
return htmlfill.render(
render('forks/fork.mako'),
defaults=errors.value,
errors=errors.error_dict or {},
prefix_error=False,
encoding="UTF-8",
force_defaults=False)
except Exception:
log.exception(
u'Exception while trying to fork the repository %s', repo_name)
msg = (
_('An error occurred during repository forking %s') %
(repo_name, ))
h.flash(msg, category='error')
raise HTTPFound(
h.route_path('repo_creating',
repo_name=form_result['repo_name_full'],
_query=dict(task_id=task_id)))

View file

@ -1340,39 +1340,6 @@ class XHRRequired(object):
return func(*fargs, **fkwargs)
class HasAcceptedRepoType(object):
"""
Check if requested repo is within given repo type aliases
"""
# TODO(marcink): remove this in favor of the predicates in pyramid routes
def __init__(self, *repo_type_list):
self.repo_type_list = set(repo_type_list)
def __call__(self, func):
return get_cython_compat_decorator(self.__wrapper, func)
def __wrapper(self, func, *fargs, **fkwargs):
import rhodecode.lib.helpers as h
cls = fargs[0]
rhodecode_repo = cls.rhodecode_repo
log.debug('%s checking repo type for %s in %s',
self.__class__.__name__,
rhodecode_repo.alias, self.repo_type_list)
if rhodecode_repo.alias in self.repo_type_list:
return func(*fargs, **fkwargs)
else:
h.flash(h.literal(
_('Action not supported for %s.' % rhodecode_repo.alias)),
category='warning')
raise HTTPFound(
h.route_path('repo_summary',
repo_name=cls.rhodecode_db_repo.repo_name))
class PermsDecorator(object):
"""
Base class for controller decorators, we extract the current user from

View file

@ -145,6 +145,10 @@ function registerRCRoutes() {
pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);

View file

@ -275,7 +275,7 @@
%endif
%if c.rhodecode_user.username != h.DEFAULT_USER:
%if c.rhodecode_db_repo.repo_type in ['git','hg']:
<li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li>
<li><a href="${h.route_path('repo_fork_new',repo_name=c.repo_name)}">${_('Fork')}</a></li>
<li><a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
%endif
%endif

View file

@ -24,7 +24,7 @@
</a>
</li>
<li>
<a title="${_('Fork')}" href="${h.url('repo_fork_home',repo_name=repo_name)}">
<a title="${_('Fork')}" href="${h.route_path('repo_fork_new',repo_name=repo_name)}">
<span>${_('Fork')}</span>
</a>
</li>

View file

@ -27,7 +27,7 @@
${self.breadcrumbs()}
</div>
${h.secure_form(h.url('repo_fork_create_home',repo_name=c.repo_info.repo_name))}
${h.secure_form(h.route_path('repo_fork_create',repo_name=c.repo_info.repo_name), method='POST', request=request)}
<div class="form">
<!-- fields -->
<div class="fields">

View file

@ -26,20 +26,94 @@
${self.repo_page_title(c.rhodecode_db_repo)}
<ul class="links">
<li>
## fork open link
<span>
<a class="btn btn-small btn-success" href="${h.url('repo_fork_home',repo_name=c.repo_name)}">
<a class="btn btn-small btn-success" href="${h.route_path('repo_fork_new',repo_name=c.repo_name)}">
${_('Create new fork')}
</a>
</span>
</li>
</ul>
</div>
<div class="table">
<div id="forks">
${c.forks_data}
</div>
<div id="fork_list_wrap">
<table id="fork_list_table" class="display"></table>
</div>
</div>
<script type="text/javascript">
$(document).ready(function() {
var $userListTable = $('#fork_list_table');
var getDatatableCount = function(){
var table = $userListTable.dataTable();
var page = table.api().page.info();
var active = page.recordsDisplay;
var total = page.recordsTotal;
var _text = _gettext("{0} out of {1} users").format(active, total);
$('#user_count').text(_text);
};
// user list
$userListTable.DataTable({
processing: true,
serverSide: true,
ajax: "${h.route_path('repo_forks_data', repo_name=c.repo_name)}",
dom: 'rtp',
pageLength: ${c.visual.dashboard_items},
order: [[ 0, "asc" ]],
columns: [
{ data: {"_": "username",
"sort": "username"}, title: "${_('Owner')}", className: "td-user" },
{ data: {"_": "fork_name",
"sort": "fork_name"}, title: "${_('Fork name')}", className: "td-email" },
{ data: {"_": "description",
"sort": "description"}, title: "${_('Description')}", className: "td-user" },
{ data: {"_": "fork_date",
"sort": "fork_date"}, title: "${_('Forked')}", className: "td-user" },
{ data: {"_": "last_activity",
"sort": "last_activity",
"type": Number}, title: "${_('Last activity')}", className: "td-time" },
{ data: {"_": "action",
"sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false }
],
language: {
paginate: DEFAULT_GRID_PAGINATION,
sProcessing: _gettext('loading...'),
emptyTable: _gettext("No forks available yet.")
},
"createdRow": function ( row, data, index ) {
if (!data['active_raw']){
$(row).addClass('closed')
}
}
});
$userListTable.on('xhr.dt', function(e, settings, json, xhr){
$userListTable.css('opacity', 1);
});
$userListTable.on('preXhr.dt', function(e, settings, data){
$userListTable.css('opacity', 0.3);
});
// refresh counters on draw
$userListTable.on('draw.dt', function(){
getDatatableCount();
});
// filter
$('#q_filter').on('keyup',
$.debounce(250, function() {
$userListTable.DataTable().search(
$('#q_filter').val()
).draw();
})
);
});
</script>
</%def>

View file

@ -1,56 +0,0 @@
## -*- coding: utf-8 -*-
<%namespace name="base" file="/base/base.mako"/>
% if c.forks_pager:
<table class="rctable fork_summary">
<tr>
<th>${_('Owner')}</th>
<th>${_('Fork')}</th>
<th>${_('Description')}</th>
<th>${_('Forked')}</th>
<th></th>
</tr>
% for f in c.forks_pager:
<tr>
<td class="td-user fork_user">
${base.gravatar_with_user(f.user.email, 16)}
</td>
<td class="td-componentname">
${h.link_to(f.repo_name,h.route_path('repo_summary',repo_name=f.repo_name))}
</td>
<td class="td-description">
<div class="truncate">${f.description}</div>
</td>
<td class="td-time follower_date">
${h.age_component(f.created_on, time_is_local=True)}
</td>
<td class="td-compare">
<a title="${h.tooltip(_('Compare fork with %s' % c.repo_name))}"
class="btn-link"
href="${h.route_path('repo_compare',
repo_name=c.repo_name,
source_ref_type=c.rhodecode_db_repo.landing_rev[0],
source_ref=c.rhodecode_db_repo.landing_rev[1],
target_ref_type=c.rhodecode_db_repo.landing_rev[0],
target_ref=c.rhodecode_db_repo.landing_rev[1],
_query=dict(merge=1, target_repo=f.repo_name))}"
>
${_('Compare fork')}
</a>
</td>
</tr>
% endfor
</table>
<div class="pagination-wh pagination-left">
<script type="text/javascript">
$(document).pjax('#forks .pager_link','#forks');
$(document).on('pjax:success',function(){
show_more_event();
timeagoActivate();
});
</script>
${c.forks_pager.pager('$link_previous ~2~ $link_next')}
</div>
% else:
${_('There are no forks yet')}
% endif

View file

@ -108,7 +108,7 @@
% endif
## forks
<a title="${_('Number of Repository Forks')}" href="${h.url('repo_forks_home', repo_name=c.repo_name)}">
<a title="${_('Number of Repository Forks')}" href="${h.route_path('repo_forks_show_all', repo_name=c.repo_name)}">
${c.repository_forks} ${_ungettext('Fork', 'Forks', c.repository_forks)}</a>,
## repo size

View file

@ -1,288 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2010-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 <http://www.gnu.org/licenses/>.
#
# 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 pytest
from rhodecode.tests import *
from rhodecode.tests.fixture import Fixture
from rhodecode.lib import helpers as h
from rhodecode.model.db import Repository
from rhodecode.model.repo import RepoModel
from rhodecode.model.user import UserModel
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
REPO_TYPE = None
NEW_REPO = None
REPO_FORK = None
@pytest.fixture(autouse=True)
def prepare(self, request, pylonsapp):
self.username = u'forkuser'
self.password = u'qweqwe'
self.u1 = fixture.create_user(self.username, password=self.password,
email=u'fork_king@rhodecode.org')
Session().commit()
self.u1id = self.u1.user_id
request.addfinalizer(self.cleanup)
def cleanup(self):
u1 = UserModel().get(self.u1id)
Session().delete(u1)
Session().commit()
def test_index(self):
self.log_user()
repo_name = self.REPO
response = self.app.get(url(controller='forks', action='forks',
repo_name=repo_name))
response.mustcontain("""There are no forks yet""")
def test_no_permissions_to_fork(self):
usr = self.log_user(TEST_USER_REGULAR_LOGIN,
TEST_USER_REGULAR_PASS)['user_id']
user_model = UserModel()
user_model.revoke_perm(usr, 'hg.fork.repository')
user_model.grant_perm(usr, 'hg.fork.none')
u = UserModel().get(usr)
u.inherit_default_permissions = False
Session().commit()
# try create a fork
repo_name = self.REPO
self.app.post(
url(controller='forks', action='fork_create', repo_name=repo_name),
{'csrf_token': self.csrf_token}, status=404)
def test_index_with_fork(self):
self.log_user()
# create a fork
fork_name = self.REPO_FORK
description = 'fork of vcs test'
repo_name = self.REPO
source_repo = Repository.get_by_repo_name(repo_name)
creation_args = {
'repo_name': fork_name,
'repo_group': '',
'fork_parent_id': source_repo.repo_id,
'repo_type': self.REPO_TYPE,
'description': description,
'private': 'False',
'landing_rev': 'rev:tip',
'csrf_token': self.csrf_token,
}
self.app.post(url(controller='forks', action='fork_create',
repo_name=repo_name), creation_args)
response = self.app.get(url(controller='forks', action='forks',
repo_name=repo_name))
response.mustcontain(
"""<a href="/%s">%s</a>""" % (fork_name, fork_name)
)
# remove this fork
fixture.destroy_repo(fork_name)
def test_fork_create_into_group(self):
self.log_user()
group = fixture.create_repo_group('vc')
group_id = group.group_id
fork_name = self.REPO_FORK
fork_name_full = 'vc/%s' % fork_name
description = 'fork of vcs test'
repo_name = self.REPO
source_repo = Repository.get_by_repo_name(repo_name)
creation_args = {
'repo_name': fork_name,
'repo_group': group_id,
'fork_parent_id': source_repo.repo_id,
'repo_type': self.REPO_TYPE,
'description': description,
'private': 'False',
'landing_rev': 'rev:tip',
'csrf_token': self.csrf_token,
}
self.app.post(url(controller='forks', action='fork_create',
repo_name=repo_name), creation_args)
repo = Repository.get_by_repo_name(fork_name_full)
assert repo.fork.repo_name == self.REPO
# run the check page that triggers the flash message
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 <a href="/%s">%s</a>'
% (repo_name, fork_name_full, fork_name_full))
# test if the fork was created in the database
fork_repo = Session().query(Repository)\
.filter(Repository.repo_name == fork_name_full).one()
assert fork_repo.repo_name == fork_name_full
assert fork_repo.fork.repo_name == repo_name
# test if the repository is visible in the list ?
response = self.app.get(h.route_path('repo_summary', repo_name=fork_name_full))
response.mustcontain(fork_name_full)
response.mustcontain(self.REPO_TYPE)
response.mustcontain('Fork of')
response.mustcontain('<a href="/%s">%s</a>' % (repo_name, repo_name))
fixture.destroy_repo(fork_name_full)
fixture.destroy_repo_group(group_id)
def test_z_fork_create(self):
self.log_user()
fork_name = self.REPO_FORK
description = 'fork of vcs test'
repo_name = self.REPO
source_repo = Repository.get_by_repo_name(repo_name)
creation_args = {
'repo_name': fork_name,
'repo_group': '',
'fork_parent_id': source_repo.repo_id,
'repo_type': self.REPO_TYPE,
'description': description,
'private': 'False',
'landing_rev': 'rev:tip',
'csrf_token': self.csrf_token,
}
self.app.post(url(controller='forks', action='fork_create',
repo_name=repo_name), creation_args)
repo = Repository.get_by_repo_name(self.REPO_FORK)
assert repo.fork.repo_name == self.REPO
# run the check page that triggers the flash message
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 <a href="/%s">%s</a>'
% (repo_name, fork_name, fork_name))
# test if the fork was created in the database
fork_repo = Session().query(Repository)\
.filter(Repository.repo_name == fork_name).one()
assert fork_repo.repo_name == fork_name
assert fork_repo.fork.repo_name == repo_name
# test if the repository is visible in the list ?
response = self.app.get(h.route_path('repo_summary', repo_name=fork_name))
response.mustcontain(fork_name)
response.mustcontain(self.REPO_TYPE)
response.mustcontain('Fork of')
response.mustcontain('<a href="/%s">%s</a>' % (repo_name, repo_name))
def test_zz_fork_permission_page(self):
usr = self.log_user(self.username, self.password)['user_id']
repo_name = self.REPO
forks = Repository.query()\
.filter(Repository.repo_type == self.REPO_TYPE)\
.filter(Repository.fork_id != None).all()
assert 1 == len(forks)
# set read permissions for this
RepoModel().grant_user_permission(repo=forks[0],
user=usr,
perm='repository.read')
Session().commit()
response = self.app.get(url(controller='forks', action='forks',
repo_name=repo_name))
response.mustcontain('fork of vcs test')
def test_zzz_fork_permission_page(self):
usr = self.log_user(self.username, self.password)['user_id']
repo_name = self.REPO
forks = Repository.query()\
.filter(Repository.repo_type == self.REPO_TYPE)\
.filter(Repository.fork_id != None).all()
assert 1 == len(forks)
# set none
RepoModel().grant_user_permission(repo=forks[0],
user=usr, perm='repository.none')
Session().commit()
# fork shouldn't be there
response = self.app.get(url(controller='forks', action='forks',
repo_name=repo_name))
response.mustcontain('There are no forks yet')
class TestGIT(_BaseTest):
REPO = GIT_REPO
NEW_REPO = NEW_GIT_REPO
REPO_TYPE = 'git'
REPO_FORK = GIT_FORK
class TestHG(_BaseTest):
REPO = HG_REPO
NEW_REPO = NEW_HG_REPO
REPO_TYPE = 'hg'
REPO_FORK = HG_FORK
@pytest.mark.usefixtures('app', 'autologin_user')
@pytest.mark.skip_backends('git','hg')
class TestSVNFork(object):
def test_fork_redirects(self, backend):
denied_actions = ['fork','fork_create']
for action in denied_actions:
response = self.app.get(url(
controller='forks', action=action,
repo_name=backend.repo_name))
assert response.status_int == 302
# Not allowed, redirect to the summary
redirected = response.follow()
summary_url = h.route_path('repo_summary', repo_name=backend.repo_name)
# URL adds leading slash and path doesn't have it
assert redirected.request.path == summary_url

View file

@ -18,15 +18,10 @@
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/
import mock
import pytest
from webob.exc import HTTPNotFound
import rhodecode
from rhodecode.model.db import Integration
from rhodecode.model.meta import Session
from rhodecode.tests import assert_session_flash, url, TEST_USER_ADMIN_LOGIN
from rhodecode.tests.utils import AssertResponse
from rhodecode.integrations import integration_type_registry
from rhodecode.config.routing import ADMIN_PREFIX