##// END OF EJS Templates
pull-requests: increase stability of concurrent pull requests creation by flushing prematurly the statuses of commits....
pull-requests: increase stability of concurrent pull requests creation by flushing prematurly the statuses of commits. This is required to increase the versions on each concurrent call. Otherwise we could get into an integrity errors of commitsha+version+repo

File last commit:

r3363:f08e98b1 default
r3368:a4f559a8 default
Show More
repo_commits.py
502 lines | 19.5 KiB | text/x-python | PythonLexer
repo-commits: ported changeset code into pyramid views....
r1951 # -*- coding: utf-8 -*-
docs: updated copyrights to 2019
r3363 # Copyright (C) 2010-2019 RhodeCode GmbH
repo-commits: ported changeset code into pyramid views....
r1951 #
# 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 collections
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
from rhodecode.lib import diffs, codeblocks
from rhodecode.lib.auth import (
LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired)
from rhodecode.lib.compat import OrderedDict
dan
diffs: introducing diff menu for whitespace toggle and context changes
r3134 from rhodecode.lib.diffs import (
cache_diff, load_cached_diff, diff_cache_exist, get_diff_context,
get_diff_whitespace_flag)
repo-commits: ported changeset code into pyramid views....
r1951 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
import rhodecode.lib.helpers as h
Bartłomiej Wołyńczyk
caching: add option to cache diffs for commits and pull requests....
r2685 from rhodecode.lib.utils2 import safe_unicode, str2bool
repo-commits: ported changeset code into pyramid views....
r1951 from rhodecode.lib.vcs.backends.base import EmptyCommit
from rhodecode.lib.vcs.exceptions import (
Bartłomiej Wołyńczyk
caching: add option to cache diffs for commits and pull requests....
r2685 RepositoryError, CommitDoesNotExistError)
repo-commits: ported changeset code into pyramid views....
r1951 from rhodecode.model.db import ChangesetComment, ChangesetStatus
from rhodecode.model.changeset_status import ChangesetStatusModel
from rhodecode.model.comment import CommentsModel
from rhodecode.model.meta import Session
Bartłomiej Wołyńczyk
caching: add option to cache diffs for commits and pull requests....
r2685 from rhodecode.model.settings import VcsSettingsModel
repo-commits: ported changeset code into pyramid views....
r1951
log = logging.getLogger(__name__)
def _update_with_GET(params, request):
for k in ['diff1', 'diff2', 'diff']:
params[k] += request.GET.getall(k)
class RepoCommitsView(RepoAppView):
def load_default_context(self):
c = self._get_local_tmpl_context(include_app_defaults=True)
c.rhodecode_repo = self.rhodecode_vcs_repo
return c
Bartłomiej Wołyńczyk
caching: add option to cache diffs for commits and pull requests....
r2685 def _is_diff_cache_enabled(self, target_repo):
caching_enabled = self._get_general_setting(
target_repo, 'rhodecode_diff_cache')
log.debug('Diff caching enabled: %s', caching_enabled)
return caching_enabled
repo-commits: ported changeset code into pyramid views....
r1951 def _commit(self, commit_id_range, method):
_ = self.request.translate
c = self.load_default_context()
c.fulldiff = self.request.GET.get('fulldiff')
# fetch global flags of ignore ws or context lines
dan
diffs: introducing diff menu for whitespace toggle and context changes
r3134 diff_context = get_diff_context(self.request)
hide_whitespace_changes = get_diff_whitespace_flag(self.request)
repo-commits: ported changeset code into pyramid views....
r1951
# diff_limit will cut off the whole diff if the limit is applied
# otherwise it will just hide the big files from the front-end
diff_limit = c.visual.cut_off_limit_diff
file_limit = c.visual.cut_off_limit_file
# get ranges of commit ids if preset
commit_range = commit_id_range.split('...')[:2]
try:
pre_load = ['affected_files', 'author', 'branch', 'date',
'message', 'parents']
if len(commit_range) == 2:
commits = self.rhodecode_vcs_repo.get_commits(
start_id=commit_range[0], end_id=commit_range[1],
pre_load=pre_load)
commits = list(commits)
else:
commits = [self.rhodecode_vcs_repo.get_commit(
commit_id=commit_id_range, pre_load=pre_load)]
c.commit_ranges = commits
if not c.commit_ranges:
raise RepositoryError(
'The commit range returned an empty result')
except CommitDoesNotExistError:
msg = _('No such commit exists for this repository')
h.flash(msg, category='error')
raise HTTPNotFound()
except Exception:
log.exception("General failure")
raise HTTPNotFound()
c.changes = OrderedDict()
c.lines_added = 0
c.lines_deleted = 0
# auto collapse if we have more than limit
collapse_limit = diffs.DiffProcessor._collapse_commits_over
c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
c.commit_statuses = ChangesetStatus.STATUSES
c.inline_comments = []
c.files = []
c.statuses = []
c.comments = []
c.unresolved_comments = []
if len(c.commit_ranges) == 1:
commit = c.commit_ranges[0]
c.comments = CommentsModel().get_comments(
self.db_repo.repo_id,
revision=commit.raw_id)
c.statuses.append(ChangesetStatusModel().get_status(
self.db_repo.repo_id, commit.raw_id))
# comments from PR
statuses = ChangesetStatusModel().get_statuses(
self.db_repo.repo_id, commit.raw_id,
with_revisions=True)
prs = set(st.pull_request for st in statuses
if st.pull_request is not None)
# from associated statuses, check the pull requests, and
# show comments from them
for pr in prs:
c.comments.extend(pr.comments)
c.unresolved_comments = CommentsModel()\
.get_commit_unresolved_todos(commit.raw_id)
diff = None
# Iterate over ranges (default commit view is always one commit)
for commit in c.commit_ranges:
c.changes[commit.raw_id] = []
commit2 = commit
pull-requests: allow to show range diff in pr view
r3124 commit1 = commit.first_parent
repo-commits: ported changeset code into pyramid views....
r1951
if method == 'show':
inline_comments = CommentsModel().get_inline_comments(
self.db_repo.repo_id, revision=commit.raw_id)
c.inline_cnt = CommentsModel().get_inline_comments_count(
inline_comments)
Bartłomiej Wołyńczyk
caching: add option to cache diffs for commits and pull requests....
r2685 c.inline_comments = inline_comments
repo-commits: ported changeset code into pyramid views....
r1951
Bartłomiej Wołyńczyk
caching: add option to cache diffs for commits and pull requests....
r2685 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(
self.db_repo)
cache_file_path = diff_cache_exist(
cache_path, 'diff', commit.raw_id,
dan
diffs: introducing diff menu for whitespace toggle and context changes
r3134 hide_whitespace_changes, diff_context, c.fulldiff)
Bartłomiej Wołyńczyk
caching: add option to cache diffs for commits and pull requests....
r2685
caching_enabled = self._is_diff_cache_enabled(self.db_repo)
force_recache = str2bool(self.request.GET.get('force_recache'))
cached_diff = None
if caching_enabled:
cached_diff = load_cached_diff(cache_file_path)
repo-commits: ported changeset code into pyramid views....
r1951
Bartłomiej Wołyńczyk
caching: add option to cache diffs for commits and pull requests....
r2685 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
if not force_recache and has_proper_diff_cache:
diffset = cached_diff['diff']
else:
vcs_diff = self.rhodecode_vcs_repo.get_diff(
commit1, commit2,
dan
diffs: introducing diff menu for whitespace toggle and context changes
r3134 ignore_whitespace=hide_whitespace_changes,
context=diff_context)
Bartłomiej Wołyńczyk
caching: add option to cache diffs for commits and pull requests....
r2685
diff_processor = diffs.DiffProcessor(
vcs_diff, format='newdiff', diff_limit=diff_limit,
file_limit=file_limit, show_full_diff=c.fulldiff)
_parsed = diff_processor.prepare()
diffset = codeblocks.DiffSet(
repo_name=self.db_repo_name,
source_node_getter=codeblocks.diffset_node_getter(commit1),
target_node_getter=codeblocks.diffset_node_getter(commit2))
diffset = self.path_filter.render_patchset_filtered(
diffset, _parsed, commit1.raw_id, commit2.raw_id)
# save cached diff
if caching_enabled:
cache_diff(cache_file_path, diffset, None)
c.limited_diff = diffset.limited_diff
repo-commits: ported changeset code into pyramid views....
r1951 c.changes[commit.raw_id] = diffset
else:
Bartłomiej Wołyńczyk
caching: add option to cache diffs for commits and pull requests....
r2685 # TODO(marcink): no cache usage here...
_diff = self.rhodecode_vcs_repo.get_diff(
commit1, commit2,
dan
diffs: introducing diff menu for whitespace toggle and context changes
r3134 ignore_whitespace=hide_whitespace_changes, context=diff_context)
Bartłomiej Wołyńczyk
caching: add option to cache diffs for commits and pull requests....
r2685 diff_processor = diffs.DiffProcessor(
_diff, format='newdiff', diff_limit=diff_limit,
file_limit=file_limit, show_full_diff=c.fulldiff)
repo-commits: ported changeset code into pyramid views....
r1951 # downloads/raw we only need RAW diff nothing else
path-permissions: Initial support for path-based permissions
r2618 diff = self.path_filter.get_raw_patch(diff_processor)
repo-commits: ported changeset code into pyramid views....
r1951 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
# sort comments by how they were generated
c.comments = sorted(c.comments, key=lambda x: x.comment_id)
if len(c.commit_ranges) == 1:
c.commit = c.commit_ranges[0]
c.parent_tmpl = ''.join(
'# Parent %s\n' % x.raw_id for x in c.commit.parents)
if method == 'download':
response = Response(diff)
response.content_type = 'text/plain'
response.content_disposition = (
'attachment; filename=%s.diff' % commit_id_range[:12])
return response
elif method == 'patch':
c.diff = safe_unicode(diff)
patch = render(
'rhodecode:templates/changeset/patch_changeset.mako',
self._get_template_context(c), self.request)
response = Response(patch)
response.content_type = 'text/plain'
return response
elif method == 'raw':
response = Response(diff)
response.content_type = 'text/plain'
return response
elif method == 'show':
if len(c.commit_ranges) == 1:
html = render(
'rhodecode:templates/changeset/changeset.mako',
self._get_template_context(c), self.request)
return Response(html)
else:
c.ancestor = None
c.target_repo = self.db_repo
html = render(
'rhodecode:templates/changeset/changeset_range.mako',
self._get_template_context(c), self.request)
return Response(html)
raise HTTPBadRequest()
@LoginRequired()
@HasRepoPermissionAnyDecorator(
'repository.read', 'repository.write', 'repository.admin')
@view_config(
route_name='repo_commit', request_method='GET',
renderer=None)
def repo_commit_show(self):
commit_id = self.request.matchdict['commit_id']
return self._commit(commit_id, method='show')
@LoginRequired()
@HasRepoPermissionAnyDecorator(
'repository.read', 'repository.write', 'repository.admin')
@view_config(
route_name='repo_commit_raw', request_method='GET',
renderer=None)
@view_config(
route_name='repo_commit_raw_deprecated', request_method='GET',
renderer=None)
def repo_commit_raw(self):
commit_id = self.request.matchdict['commit_id']
return self._commit(commit_id, method='raw')
@LoginRequired()
@HasRepoPermissionAnyDecorator(
'repository.read', 'repository.write', 'repository.admin')
@view_config(
route_name='repo_commit_patch', request_method='GET',
renderer=None)
def repo_commit_patch(self):
commit_id = self.request.matchdict['commit_id']
return self._commit(commit_id, method='patch')
@LoginRequired()
@HasRepoPermissionAnyDecorator(
'repository.read', 'repository.write', 'repository.admin')
@view_config(
route_name='repo_commit_download', request_method='GET',
renderer=None)
def repo_commit_download(self):
commit_id = self.request.matchdict['commit_id']
return self._commit(commit_id, method='download')
@LoginRequired()
@NotAnonymous()
@HasRepoPermissionAnyDecorator(
'repository.read', 'repository.write', 'repository.admin')
@CSRFRequired()
@view_config(
route_name='repo_commit_comment_create', request_method='POST',
renderer='json_ext')
def repo_commit_comment_create(self):
_ = self.request.translate
commit_id = self.request.matchdict['commit_id']
c = self.load_default_context()
status = self.request.POST.get('changeset_status', None)
text = self.request.POST.get('text')
comment_type = self.request.POST.get('comment_type')
resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
if status:
text = text or (_('Status change %(transition_icon)s %(status)s')
% {'transition_icon': '>',
'status': ChangesetStatus.get_status_lbl(status)})
multi_commit_ids = []
for _commit_id in self.request.POST.get('commit_ids', '').split(','):
if _commit_id not in ['', None, EmptyCommit.raw_id]:
if _commit_id not in multi_commit_ids:
multi_commit_ids.append(_commit_id)
commit_ids = multi_commit_ids or [commit_id]
comment = None
for current_id in filter(None, commit_ids):
comment = CommentsModel().create(
text=text,
repo=self.db_repo.repo_id,
user=self._rhodecode_db_user.user_id,
commit_id=current_id,
f_path=self.request.POST.get('f_path'),
line_no=self.request.POST.get('line'),
status_change=(ChangesetStatus.get_status_lbl(status)
if status else None),
status_change_type=status,
comment_type=comment_type,
audit-logs: store properly IP and user for certain comments types....
r2728 resolves_comment_id=resolves_comment_id,
auth_user=self._rhodecode_user
repo-commits: ported changeset code into pyramid views....
r1951 )
# get status if set !
if status:
# if latest status was from pull request and it's closed
# disallow changing status !
# dont_allow_on_closed_pull_request = True !
try:
ChangesetStatusModel().set_status(
self.db_repo.repo_id,
status,
self._rhodecode_db_user.user_id,
comment,
revision=current_id,
dont_allow_on_closed_pull_request=True
)
except StatusChangeOnClosedPullRequestError:
msg = _('Changing the status of a commit associated with '
'a closed pull request is not allowed')
log.exception(msg)
h.flash(msg, category='warning')
raise HTTPFound(h.route_path(
'repo_commit', repo_name=self.db_repo_name,
commit_id=current_id))
# finalize, commit and redirect
Session().commit()
data = {
'target_id': h.safeid(h.safe_unicode(
self.request.POST.get('f_path'))),
}
if comment:
c.co = comment
rendered_comment = render(
'rhodecode:templates/changeset/changeset_comment_block.mako',
self._get_template_context(c), self.request)
data.update(comment.get_dict())
data.update({'rendered_text': rendered_comment})
return data
@LoginRequired()
@NotAnonymous()
@HasRepoPermissionAnyDecorator(
'repository.read', 'repository.write', 'repository.admin')
@CSRFRequired()
@view_config(
route_name='repo_commit_comment_preview', request_method='POST',
renderer='string', xhr=True)
def repo_commit_comment_preview(self):
# Technically a CSRF token is not needed as no state changes with this
# call. However, as this is a POST is better to have it, so automated
# tools don't flag it as potential CSRF.
# Post is required because the payload could be bigger than the maximum
# allowed by GET.
text = self.request.POST.get('text')
renderer = self.request.POST.get('renderer') or 'rst'
if text:
return h.render(text, renderer=renderer, mentions=True)
return ''
@LoginRequired()
@NotAnonymous()
@HasRepoPermissionAnyDecorator(
'repository.read', 'repository.write', 'repository.admin')
@CSRFRequired()
@view_config(
route_name='repo_commit_comment_delete', request_method='POST',
renderer='json_ext')
def repo_commit_comment_delete(self):
commit_id = self.request.matchdict['commit_id']
comment_id = self.request.matchdict['comment_id']
core: no longer rely on webob exception inside get_or_404 function....
r1956 comment = ChangesetComment.get_or_404(comment_id)
repo-commits: ported changeset code into pyramid views....
r1951 if not comment:
log.debug('Comment with id:%s not found, skipping', comment_id)
# comment already deleted in another call probably
return True
is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
super_admin = h.HasPermissionAny('hg.admin')()
comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
is_repo_comment = comment.repo.repo_name == self.db_repo_name
comment_repo_admin = is_repo_admin and is_repo_comment
if super_admin or comment_owner or comment_repo_admin:
audit-logs: store properly IP and user for certain comments types....
r2728 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
repo-commits: ported changeset code into pyramid views....
r1951 Session().commit()
return True
else:
log.warning('No permissions for user %s to delete comment_id: %s',
self._rhodecode_db_user, comment_id)
raise HTTPNotFound()
@LoginRequired()
@HasRepoPermissionAnyDecorator(
'repository.read', 'repository.write', 'repository.admin')
@view_config(
route_name='repo_commit_data', request_method='GET',
renderer='json_ext', xhr=True)
def repo_commit_data(self):
commit_id = self.request.matchdict['commit_id']
self.load_default_context()
try:
return self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
except CommitDoesNotExistError as e:
return EmptyCommit(message=str(e))
@LoginRequired()
@HasRepoPermissionAnyDecorator(
'repository.read', 'repository.write', 'repository.admin')
@view_config(
route_name='repo_commit_children', request_method='GET',
renderer='json_ext', xhr=True)
def repo_commit_children(self):
commit_id = self.request.matchdict['commit_id']
self.load_default_context()
commits: add protection against 500 errors on commit children/parents page.
r2150 try:
commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
children = commit.children
except CommitDoesNotExistError:
children = []
result = {"results": children}
repo-commits: ported changeset code into pyramid views....
r1951 return result
@LoginRequired()
@HasRepoPermissionAnyDecorator(
'repository.read', 'repository.write', 'repository.admin')
@view_config(
route_name='repo_commit_parents', request_method='GET',
renderer='json_ext')
def repo_commit_parents(self):
commit_id = self.request.matchdict['commit_id']
self.load_default_context()
commits: add protection against 500 errors on commit children/parents page.
r2150 try:
commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
parents = commit.parents
except CommitDoesNotExistError:
parents = []
result = {"results": parents}
repo-commits: ported changeset code into pyramid views....
r1951 return result