repo_forks.py
252 lines
| 9.5 KiB
| text/x-python
|
PythonLexer
r5088 | # Copyright (C) 2011-2023 RhodeCode GmbH | |||
r1988 | # | |||
# 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 | ||||
r2080 | import formencode.htmlfill | |||
from pyramid.httpexceptions import HTTPFound | ||||
r4610 | ||||
r1988 | from pyramid.renderers import render | |||
from pyramid.response import Response | ||||
r2874 | from rhodecode import events | |||
r1988 | from rhodecode.apps._base import RepoAppView, DataGridAppView | |||
from rhodecode.lib.auth import ( | ||||
LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, | ||||
HasRepoPermissionAny, HasPermissionAnyDecorator, CSRFRequired) | ||||
import rhodecode.lib.helpers as h | ||||
r5065 | from rhodecode.lib.str_utils import safe_str | |||
r2359 | from rhodecode.lib.celerylib.utils import get_task_id | |||
r2080 | from rhodecode.model.db import coalesce, or_, Repository, RepoGroup | |||
r3824 | from rhodecode.model.permission import PermissionModel | |||
r1988 | from rhodecode.model.repo import RepoModel | |||
from rhodecode.model.forms import RepoForkForm | ||||
from rhodecode.model.scm import ScmModel, RepoGroupList | ||||
log = logging.getLogger(__name__) | ||||
class RepoForksView(RepoAppView, DataGridAppView): | ||||
def load_default_context(self): | ||||
c = self._get_local_tmpl_context(include_app_defaults=True) | ||||
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) | ||||
r5065 | c.repo_groups_choices = list(map(lambda k: safe_str(k[0]), c.repo_groups)) | |||
r3881 | ||||
r1988 | c.personal_repo_group = c.rhodecode_user.personal_repo_group | |||
return c | ||||
@LoginRequired() | ||||
@HasRepoPermissionAnyDecorator( | ||||
'repository.read', 'repository.write', 'repository.admin') | ||||
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') | ||||
def repo_forks_data(self): | ||||
_ = self.request.translate | ||||
r2358 | self.load_default_context() | |||
r1988 | 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 | ||||
r2176 | allowed_ids = [-1] | |||
r1988 | 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: | ||||
r5093 | like_expression = f'%{safe_str(search_q)}%' | |||
r1988 | 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, | ||||
r4370 | source_ref_type=self.db_repo.landing_ref_type, | |||
source_ref=self.db_repo.landing_ref_name, | ||||
target_ref_type=self.db_repo.landing_ref_type, | ||||
target_ref=self.db_repo.landing_ref_name, | ||||
r1988 | _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), | ||||
r2996 | "description": fork.description_safe, | |||
r1988 | "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() | ||||
r4663 | @HasPermissionAnyDecorator('hg.admin', PermissionModel.FORKING_ENABLED) | |||
r1988 | @HasRepoPermissionAnyDecorator( | |||
'repository.read', 'repository.write', 'repository.admin') | ||||
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'] = ( | ||||
r5093 | 'fork of repository: {} \n{}'.format( | |||
r1988 | 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() | ||||
r4663 | @HasPermissionAnyDecorator('hg.admin', PermissionModel.FORKING_ENABLED) | |||
r1988 | @HasRepoPermissionAnyDecorator( | |||
'repository.read', 'repository.write', 'repository.admin') | ||||
@CSRFRequired() | ||||
def repo_fork_create(self): | ||||
_ = self.request.translate | ||||
c = self.load_default_context() | ||||
r3881 | _form = RepoForkForm(self.request.translate, | |||
old_data={'repo_type': self.db_repo.repo_type}, | ||||
repo_groups=c.repo_groups_choices)() | ||||
r2172 | post_data = dict(self.request.POST) | |||
# forbid injecting other repo by forging a request | ||||
post_data['fork_parent_id'] = self.db_repo.repo_id | ||||
r3969 | post_data['landing_rev'] = self.db_repo._landing_revision | |||
r2172 | ||||
r1988 | form_result = {} | |||
task_id = None | ||||
try: | ||||
r2172 | form_result = _form.to_python(post_data) | |||
r3411 | copy_permissions = form_result.get('copy_permissions') | |||
r1988 | # 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) | ||||
r2359 | ||||
task_id = get_task_id(task) | ||||
r1988 | except formencode.Invalid as errors: | |||
r2081 | c.rhodecode_db_repo = self.db_repo | |||
r1988 | ||||
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( | ||||
r5093 | 'Exception while trying to fork the repository %s', self.db_repo_name) | |||
r3411 | msg = _('An error occurred during repository forking %s') % (self.db_repo_name, ) | |||
r1988 | h.flash(msg, category='error') | |||
r3411 | raise HTTPFound(h.route_path('home')) | |||
r1988 | ||||
repo_name = form_result.get('repo_name_full', self.db_repo_name) | ||||
r2874 | ||||
r3411 | affected_user_ids = [self._rhodecode_user.user_id] | |||
if copy_permissions: | ||||
r3413 | # permission flush is done in repo creating | |||
pass | ||||
r3411 | ||||
r3824 | PermissionModel().trigger_permission_flush(affected_user_ids) | |||
r2874 | ||||
r1988 | raise HTTPFound( | |||
r3411 | h.route_path('repo_creating', repo_name=repo_name, | |||
r1988 | _query=dict(task_id=task_id))) | |||