##// END OF EJS Templates
chore(release): merged default into stable branch
chore(release): merged default into stable branch

File last commit:

r5516:3496180b default
r5558:77c2b736 merge v5.3.0 stable
Show More
repo_commits.py
830 lines | 33.8 KiB | text/x-python | PythonLexer
# Copyright (C) 2010-2023 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 collections
from pyramid.httpexceptions import (
HTTPNotFound, HTTPBadRequest, HTTPFound, HTTPForbidden, HTTPConflict)
from pyramid.renderers import render
from pyramid.response import Response
from rhodecode.apps._base import RepoAppView
from rhodecode.apps.file_store import utils as store_utils
from rhodecode.apps.file_store.exceptions import FileNotAllowedException, FileOverSizeException
from rhodecode.lib import diffs, codeblocks, channelstream
from rhodecode.lib.auth import (
LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired)
from rhodecode.lib import ext_json
from collections import OrderedDict
from rhodecode.lib.diffs import (
cache_diff, load_cached_diff, diff_cache_exist, get_diff_context,
get_diff_whitespace_flag)
from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError, CommentVersionMismatch
import rhodecode.lib.helpers as h
from rhodecode.lib.utils2 import str2bool, StrictAttributeDict, safe_str
from rhodecode.lib.vcs.backends.base import EmptyCommit
from rhodecode.lib.vcs.exceptions import (
RepositoryError, CommitDoesNotExistError)
from rhodecode.model.db import ChangesetComment, ChangesetStatus, FileStore, \
ChangesetCommentHistory
from rhodecode.model.changeset_status import ChangesetStatusModel
from rhodecode.model.comment import CommentsModel
from rhodecode.model.meta import Session
from rhodecode.model.settings import VcsSettingsModel
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
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
def _commit(self, commit_id_range, method):
_ = self.request.translate
c = self.load_default_context()
c.fulldiff = self.request.GET.get('fulldiff')
redirect_to_combined = str2bool(self.request.GET.get('redirect_combined'))
# fetch global flags of ignore ws or context lines
diff_context = get_diff_context(self.request)
hide_whitespace_changes = get_diff_whitespace_flag(self.request)
# 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 self.rhodecode_vcs_repo.alias == 'hg':
pre_load += ['hidden', 'obsolete', 'phase']
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, translate_tags=False)
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 as e:
msg = _('No such commit exists. Org exception: `{}`').format(safe_str(e))
h.flash(msg, category='error')
raise HTTPNotFound()
except Exception:
log.exception("General failure")
raise HTTPNotFound()
single_commit = len(c.commit_ranges) == 1
if redirect_to_combined and not single_commit:
source_ref = getattr(c.commit_ranges[0].parents[0]
if c.commit_ranges[0].parents else h.EmptyCommit(), 'raw_id')
target_ref = c.commit_ranges[-1].raw_id
next_url = h.route_path(
'repo_compare',
repo_name=c.repo_name,
source_ref_type='rev',
source_ref=source_ref,
target_ref_type='rev',
target_ref=target_ref)
raise HTTPFound(next_url)
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.comments = []
c.unresolved_comments = []
c.resolved_comments = []
# Single commit
if single_commit:
commit = c.commit_ranges[0]
c.comments = CommentsModel().get_comments(
self.db_repo.repo_id,
revision=commit.raw_id)
# comments from PR
statuses = ChangesetStatusModel().get_statuses(
self.db_repo.repo_id, commit.raw_id,
with_revisions=True)
prs = set()
reviewers = list()
reviewers_duplicates = set() # to not have duplicates from multiple votes
for c_status in statuses:
# extract associated pull-requests from votes
if c_status.pull_request:
prs.add(c_status.pull_request)
# extract reviewers
_user_id = c_status.author.user_id
if _user_id not in reviewers_duplicates:
reviewers.append(
StrictAttributeDict({
'user': c_status.author,
# fake attributed for commit, page that we don't have
# but we share the display with PR page
'mandatory': False,
'reasons': [],
'rule_user_group_data': lambda: None
})
)
reviewers_duplicates.add(_user_id)
c.reviewers_count = len(reviewers)
c.observers_count = 0
# 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)
c.resolved_comments = CommentsModel()\
.get_commit_resolved_todos(commit.raw_id)
c.inline_comments_flat = CommentsModel()\
.get_commit_inline_comments(commit.raw_id)
review_statuses = ChangesetStatusModel().aggregate_votes_by_user(
statuses, reviewers)
c.commit_review_status = ChangesetStatus.STATUS_NOT_REVIEWED
c.commit_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
for review_obj, member, reasons, mandatory, status in review_statuses:
member_reviewer = h.reviewer_as_json(
member, reasons=reasons, mandatory=mandatory, role=None,
user_group=None
)
current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED
member_reviewer['review_status'] = current_review_status
member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status)
member_reviewer['allowed_to_update'] = False
c.commit_set_reviewers_data_json['reviewers'].append(member_reviewer)
c.commit_set_reviewers_data_json = ext_json.str_json(c.commit_set_reviewers_data_json)
# NOTE(marcink): this uses the same voting logic as in pull-requests
c.commit_review_status = ChangesetStatusModel().calculate_status(review_statuses)
c.commit_broadcast_channel = channelstream.comment_channel(c.repo_name, commit_obj=commit)
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
commit1 = commit.first_parent
if method == 'show':
inline_comments = CommentsModel().get_inline_comments(
self.db_repo.repo_id, revision=commit.raw_id)
c.inline_cnt = len(CommentsModel().get_inline_comments_as_list(
inline_comments))
c.inline_comments = inline_comments
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,
hide_whitespace_changes, diff_context, c.fulldiff)
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)
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,
ignore_whitespace=hide_whitespace_changes,
context=diff_context)
diff_processor = diffs.DiffProcessor(vcs_diff, 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
c.changes[commit.raw_id] = diffset
else:
# TODO(marcink): no cache usage here...
_diff = self.rhodecode_vcs_repo.get_diff(
commit1, commit2,
ignore_whitespace=hide_whitespace_changes, context=diff_context)
diff_processor = diffs.DiffProcessor(_diff, diff_format='newdiff',
diff_limit=diff_limit,
file_limit=file_limit, show_full_diff=c.fulldiff)
# downloads/raw we only need RAW diff nothing else
diff = self.path_filter.get_raw_patch(diff_processor)
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)
c.at_version_num = None
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_str(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')
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')
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')
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')
def repo_commit_download(self):
commit_id = self.request.matchdict['commit_id']
return self._commit(commit_id, method='download')
def _commit_comments_create(self, commit_id, comments):
_ = self.request.translate
data = {}
if not comments:
return
commit = self.db_repo.get_commit(commit_id)
all_drafts = len([x for x in comments if str2bool(x['is_draft'])]) == len(comments)
for entry in comments:
c = self.load_default_context()
comment_type = entry['comment_type']
text = entry['text']
status = entry['status']
is_draft = str2bool(entry['is_draft'])
resolves_comment_id = entry['resolves_comment_id']
f_path = entry['f_path']
line_no = entry['line']
target_elem_id = f'file-{h.safeid(h.safe_str(f_path))}'
if status:
text = text or (_('Status change %(transition_icon)s %(status)s')
% {'transition_icon': '>',
'status': ChangesetStatus.get_status_lbl(status)})
comment = CommentsModel().create(
text=text,
repo=self.db_repo.repo_id,
user=self._rhodecode_db_user.user_id,
commit_id=commit_id,
f_path=f_path,
line_no=line_no,
status_change=(ChangesetStatus.get_status_lbl(status)
if status else None),
status_change_type=status,
comment_type=comment_type,
is_draft=is_draft,
resolves_comment_id=resolves_comment_id,
auth_user=self._rhodecode_user,
send_email=not is_draft, # skip notification for draft comments
)
is_inline = comment.is_inline
# get status if set !
if status:
# `dont_allow_on_closed_pull_request = True` means
# if latest status was from pull request and it's closed
# disallow changing status !
try:
ChangesetStatusModel().set_status(
self.db_repo.repo_id,
status,
self._rhodecode_db_user.user_id,
comment,
revision=commit_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=commit_id))
Session().flush()
# this is somehow required to get access to some relationship
# loaded on comment
Session().refresh(comment)
# skip notifications for drafts
if not is_draft:
CommentsModel().trigger_commit_comment_hook(
self.db_repo, self._rhodecode_user, 'create',
data={'comment': comment, 'commit': commit})
comment_id = comment.comment_id
data[comment_id] = {
'target_id': target_elem_id
}
Session().flush()
c.co = comment
c.at_version_num = 0
c.is_new = True
rendered_comment = render(
'rhodecode:templates/changeset/changeset_comment_block.mako',
self._get_template_context(c), self.request)
data[comment_id].update(comment.get_dict())
data[comment_id].update({'rendered_text': rendered_comment})
# finalize, commit and redirect
Session().commit()
# skip channelstream for draft comments
if not all_drafts:
comment_broadcast_channel = channelstream.comment_channel(
self.db_repo_name, commit_obj=commit)
comment_data = data
posted_comment_type = 'inline' if is_inline else 'general'
if len(data) == 1:
msg = _('posted {} new {} comment').format(len(data), posted_comment_type)
else:
msg = _('posted {} new {} comments').format(len(data), posted_comment_type)
channelstream.comment_channelstream_push(
self.request, comment_broadcast_channel, self._rhodecode_user, msg,
comment_data=comment_data)
return data
@LoginRequired()
@NotAnonymous()
@HasRepoPermissionAnyDecorator(
'repository.read', 'repository.write', 'repository.admin')
@CSRFRequired()
def repo_commit_comment_create(self):
_ = self.request.translate
commit_id = self.request.matchdict['commit_id']
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]
data = []
# Multiple comments for each passed commit id
for current_id in filter(None, commit_ids):
comment_data = {
'comment_type': self.request.POST.get('comment_type'),
'text': self.request.POST.get('text'),
'status': self.request.POST.get('changeset_status', None),
'is_draft': self.request.POST.get('draft'),
'resolves_comment_id': self.request.POST.get('resolves_comment_id', None),
'close_pull_request': self.request.POST.get('close_pull_request'),
'f_path': self.request.POST.get('f_path'),
'line': self.request.POST.get('line'),
}
comment = self._commit_comments_create(commit_id=current_id, comments=[comment_data])
data.append(comment)
return data if len(data) > 1 else data[0]
@LoginRequired()
@NotAnonymous()
@HasRepoPermissionAnyDecorator(
'repository.read', 'repository.write', 'repository.admin')
@CSRFRequired()
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,
repo_name=self.db_repo_name)
return ''
@LoginRequired()
@HasRepoPermissionAnyDecorator(
'repository.read', 'repository.write', 'repository.admin')
@CSRFRequired()
def repo_commit_comment_history_view(self):
c = self.load_default_context()
comment_id = self.request.matchdict['comment_id']
comment_history_id = self.request.matchdict['comment_history_id']
comment = ChangesetComment.get_or_404(comment_id)
comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
if comment.draft and not comment_owner:
# if we see draft comments history, we only allow this for owner
raise HTTPNotFound()
comment_history = ChangesetCommentHistory.get_or_404(comment_history_id)
is_repo_comment = comment_history.comment.repo.repo_id == self.db_repo.repo_id
if is_repo_comment:
c.comment_history = comment_history
rendered_comment = render(
'rhodecode:templates/changeset/comment_history.mako',
self._get_template_context(c), self.request)
return rendered_comment
else:
log.warning('No permissions for user %s to show comment_history_id: %s',
self._rhodecode_db_user, comment_history_id)
raise HTTPNotFound()
@LoginRequired()
@NotAnonymous()
@HasRepoPermissionAnyDecorator(
'repository.read', 'repository.write', 'repository.admin')
@CSRFRequired()
def repo_commit_comment_attachment_upload(self):
c = self.load_default_context()
upload_key = 'attachment'
file_obj = self.request.POST.get(upload_key)
if file_obj is None:
self.request.response.status = 400
return {'store_fid': None,
'access_path': None,
'error': f'{upload_key} data field is missing'}
if not hasattr(file_obj, 'filename'):
self.request.response.status = 400
return {'store_fid': None,
'access_path': None,
'error': 'filename cannot be read from the data field'}
filename = file_obj.filename
file_display_name = filename
metadata = {
'user_uploaded': {'username': self._rhodecode_user.username,
'user_id': self._rhodecode_user.user_id,
'ip': self._rhodecode_user.ip_addr}}
# TODO(marcink): allow .ini configuration for allowed_extensions, and file-size
allowed_extensions = [
'gif', '.jpeg', '.jpg', '.png', '.docx', '.gz', '.log', '.pdf',
'.pptx', '.txt', '.xlsx', '.zip']
max_file_size = 10 * 1024 * 1024 # 10MB, also validated via dropzone.js
try:
f_store = store_utils.get_filestore_backend(self.request.registry.settings)
store_uid, metadata = f_store.store(
filename, file_obj.file, metadata=metadata,
extensions=allowed_extensions, max_filesize=max_file_size)
except FileNotAllowedException:
self.request.response.status = 400
permitted_extensions = ', '.join(allowed_extensions)
error_msg = f'File `{filename}` is not allowed. ' \
f'Only following extensions are permitted: {permitted_extensions}'
return {'store_fid': None,
'access_path': None,
'error': error_msg}
except FileOverSizeException:
self.request.response.status = 400
limit_mb = h.format_byte_size_binary(max_file_size)
error_msg = f'File {filename} is exceeding allowed limit of {limit_mb}.'
return {'store_fid': None,
'access_path': None,
'error': error_msg}
try:
entry = FileStore.create(
file_uid=store_uid, filename=metadata["filename"],
file_hash=metadata["sha256"], file_size=metadata["size"],
file_display_name=file_display_name,
file_description=f'comment attachment `{safe_str(filename)}`',
hidden=True, check_acl=True, user_id=self._rhodecode_user.user_id,
scope_repo_id=self.db_repo.repo_id
)
Session().add(entry)
Session().commit()
log.debug('Stored upload in DB as %s', entry)
except Exception:
log.exception('Failed to store file %s', filename)
self.request.response.status = 400
return {'store_fid': None,
'access_path': None,
'error': f'File {filename} failed to store in DB.'}
Session().commit()
data = {
'store_fid': store_uid,
'access_path': h.route_path(
'download_file', fid=store_uid),
'fqn_access_path': h.route_url(
'download_file', fid=store_uid),
# for EE those are replaced by FQN links on repo-only like
'repo_access_path': h.route_url(
'download_file', fid=store_uid),
'repo_fqn_access_path': h.route_url(
'download_file', fid=store_uid),
}
# this data is a part of CE/EE additional code
if c.rhodecode_edition_id == 'EE':
data.update({
'repo_access_path': h.route_path(
'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
'repo_fqn_access_path': h.route_url(
'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
})
return data
@LoginRequired()
@NotAnonymous()
@HasRepoPermissionAnyDecorator(
'repository.read', 'repository.write', 'repository.admin')
@CSRFRequired()
def repo_commit_comment_delete(self):
commit_id = self.request.matchdict['commit_id']
comment_id = self.request.matchdict['comment_id']
comment = ChangesetComment.get_or_404(comment_id)
if not comment:
log.debug('Comment with id:%s not found, skipping', comment_id)
# comment already deleted in another call probably
return True
if comment.immutable:
# don't allow deleting comments that are immutable
raise HTTPForbidden()
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_id == self.db_repo.repo_id
comment_repo_admin = is_repo_admin and is_repo_comment
if comment.draft and not comment_owner:
# We never allow to delete draft comments for other than owners
raise HTTPNotFound()
if super_admin or comment_owner or comment_repo_admin:
CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
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()
@NotAnonymous()
@HasRepoPermissionAnyDecorator(
'repository.read', 'repository.write', 'repository.admin')
@CSRFRequired()
def repo_commit_comment_edit(self):
self.load_default_context()
commit_id = self.request.matchdict['commit_id']
comment_id = self.request.matchdict['comment_id']
comment = ChangesetComment.get_or_404(comment_id)
if comment.immutable:
# don't allow deleting comments that are immutable
raise HTTPForbidden()
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_id == self.db_repo.repo_id
comment_repo_admin = is_repo_admin and is_repo_comment
if super_admin or comment_owner or comment_repo_admin:
text = self.request.POST.get('text')
version = self.request.POST.get('version')
if text == comment.text:
log.warning(
'Comment(repo): '
'Trying to create new version '
'with the same comment body {}'.format(
comment_id,
)
)
raise HTTPNotFound()
if version.isdigit():
version = int(version)
else:
log.warning(
'Comment(repo): Wrong version type {} {} '
'for comment {}'.format(
version,
type(version),
comment_id,
)
)
raise HTTPNotFound()
try:
comment_history = CommentsModel().edit(
comment_id=comment_id,
text=text,
auth_user=self._rhodecode_user,
version=version,
)
except CommentVersionMismatch:
raise HTTPConflict()
if not comment_history:
raise HTTPNotFound()
if not comment.draft:
commit = self.db_repo.get_commit(commit_id)
CommentsModel().trigger_commit_comment_hook(
self.db_repo, self._rhodecode_user, 'edit',
data={'comment': comment, 'commit': commit})
Session().commit()
return {
'comment_history_id': comment_history.comment_history_id,
'comment_id': comment.comment_id,
'comment_version': comment_history.version,
'comment_author_username': comment_history.author.username,
'comment_author_gravatar': h.gravatar_url(comment_history.author.email, 16, request=self.request),
'comment_created_on': h.age_component(comment_history.created_on,
time_is_local=True),
}
else:
log.warning('No permissions for user %s to edit comment_id: %s',
self._rhodecode_db_user, comment_id)
raise HTTPNotFound()
@LoginRequired()
@HasRepoPermissionAnyDecorator(
'repository.read', 'repository.write', 'repository.admin')
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')
def repo_commit_children(self):
commit_id = self.request.matchdict['commit_id']
self.load_default_context()
try:
commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
children = commit.children
except CommitDoesNotExistError:
children = []
result = {"results": children}
return result
@LoginRequired()
@HasRepoPermissionAnyDecorator(
'repository.read', 'repository.write', 'repository.admin')
def repo_commit_parents(self):
commit_id = self.request.matchdict['commit_id']
self.load_default_context()
try:
commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
parents = commit.parents
except CommitDoesNotExistError:
parents = []
result = {"results": parents}
return result